
import React from 'react';
import { ContentPage } from '../../components/ContentPage';
import { InfoPanel } from '../../components/InfoPanel';
import { ApiSection } from '../../types';

const computedApi: ApiSection[] = [
  {
    title: 'Chemical.Computed',
    description: 'Creates a reactive value that is derived from other reactive Values or Computeds. It automatically recalculates its value whenever any of its dependencies change.',
    entries: [
      {
        signature: 'Chemical.Computed<T>(computeFn: () => T, cleanupFn?: (oldValue: T) => ()): Computed<T>',
        description: 'Constructs a new Computed value.',
        parameters: [
          { name: 'computeFn', type: '() => T', description: 'A function that calculates the value. Any reactive objects accessed via `:get()` inside this function become dependencies.' },
          { name: 'cleanupFn', type: '((oldValue: T) => ())?', description: "An optional function. When a dependency of the `Computed` changes, this `cleanupFn` is called with the `Computed`'s *previous* value right *before* the main `computeFn` is re-executed to calculate the new value. The `cleanupFn` is also called with the last computed value when the `Computed` object itself is destroyed." },
        ],
        returns: { type: 'Computed<T>', description: 'A new Computed object.' },
        example: `
local Chemical = require(game.ReplicatedStorage.Chemical)
local Value = Chemical.Value
local Computed = Chemical.Computed
local Effect = Chemical.Effect

local price = Value(100)
local discount = Value(0.1) -- 10% discount

local finalPrice = Computed(function()
  print("Debug: Recalculating final price...")
  return price:get() * (1 - discount:get())
end, function(oldFinalPrice)
  -- This cleanupFn receives the value 'finalPrice' held *before* this re-computation.
  print("Debug: Cleanup - Old final price was:", oldFinalPrice)
end)

-- The 'finalPrice' Computed has its 'computeFn' run once upon creation (or lazily on first :get()).
print("Initial final price (after first computation):", finalPrice:get())
-- Expected:
-- Debug: Recalculating final price...
-- Initial final price (after first computation): 90

price:set(200) -- Dependency changed
-- IMPORTANT: 'finalPrice:get()' here will return the *old* value (90)
-- because the 'finalPrice' Computed has not re-evaluated yet.
-- Re-evaluation is batched and occurs in the Chemical scheduler's next tick.
print("Final price immediately after price:set(200):", finalPrice:get())
-- Expected Output: Final price immediately after price:set(200): 90

-- An Effect depending on 'finalPrice' will see the updated value in a subsequent scheduler tick.
-- The scheduler will typically:
-- 1. Detect 'price' changed.
-- 2. Mark 'finalPrice' (Computed) as dirty.
-- 3. In the next update cycle, process 'finalPrice':
--    a. Call cleanupFn for 'finalPrice' with its old value (90).
--    b. Call computeFn for 'finalPrice' to get the new value (180).
-- 4. Mark 'priceEffect' (Effect) as dirty because 'finalPrice' changed.
-- 5. Process 'priceEffect', which will then :get() the new value of 'finalPrice' (180).
local priceEffect = Effect(function()
    print("Effect: finalPrice is now:", finalPrice:get())
end)

-- Expected output sequence from scheduler after price:set(200):
-- Debug: Cleanup - Old final price was: 90
-- Debug: Recalculating final price...
-- Effect: finalPrice is now: 180
        `,
      },
    ],
  },
  {
    title: 'Computed Methods',
    entries: [
      {
        signature: 'computed:get(): T',
        description: 'Retrieves the current (potentially cached) value of the Computed. If called within another Computed or Effect, it establishes a dependency. Does not trigger re-computation itself; re-computation is handled by the scheduler when dependencies change.',
        returns: { type: 'T', description: 'The current computed value.' },
        example: `
local Chemical = require(game.ReplicatedStorage.Chemical)
local Value = Chemical.Value
local Computed = Chemical.Computed

local x = Value(5)
local y = Value(10)
local sum = Computed(function() return x:get() + y:get() end)

local currentSum = sum:get() -- currentSum is 15
        `,
      },
      {
        signature: 'computed:destroy()',
        description: 'Destroys the Computed object. If a `cleanupFn` was provided during construction, it will be called with the last computed value. This also removes its dependencies on other reactive objects.',
        example: `
local Chemical = require(game.ReplicatedStorage.Chemical)
local Value = Chemical.Value
local Computed = Chemical.Computed

local name = Value("Test")
local upperName = Computed(function() return name:get():upper() end, function(oldUpper) print("Cleaning up:", oldUpper) end)
-- ...
upperName:destroy() -- "Cleaning up: TEST" will be printed if "TEST" was the last computed value.
        `,
      },
      {
        signature: 'computed:clean()',
        description: 'Explicitly runs the `cleanupFn` if one was provided, using the current value of the Computed. This is typically managed internally but can be called manually if needed.',
        example: `
local Chemical = require(game.ReplicatedStorage.Chemical)
local Value = Chemical.Value
local Computed = Chemical.Computed

local data = Value({ value = 1 })
local processed = Computed(function()
  return data:get().value * 2
end, function(oldProcessedValue)
  print("Cleaning processed value:", oldProcessedValue)
end)

processed:get() -- Ensure it's computed once, current value is 2
processed:clean() -- "Cleaning processed value: 2"
        `,
        notes: "This method is less commonly used directly. Destruction or dependency changes usually handle cleanup.",
      },
    ],
  },
];

export const ComputedPage: React.FC = () => {
  return (
    <ContentPage title="Chemical.Computed" sections={computedApi}>
      <InfoPanel type="note" title="Execution Timing & Batching">
        <p>
          Computed value updates are batched and processed asynchronously by the Chemical scheduler,
          typically at the end of the current frame or scheduler tick.
        </p>
        <p className="mt-2">
          If a dependency changes multiple times within a single frame, the Computed will only
          re-calculate <strong>once</strong> using the <strong>most recent (final) values</strong> of its
          dependencies when the scheduler processes the batch.
        </p>
        <p className="mt-2">
          Consequently, reading a Computed value (e.g., <code>myComputed:get()</code>) immediately
          after one of its dependencies is set will yield its <strong>old value</strong>. Effects and
          other Computeds depending on it will receive the updated value in the subsequent
          scheduler cycle.
        </p>
      </InfoPanel>
    </ContentPage>
  );
};
