import { Effect, Option } from "effect";

import { MarimoConfigurationService } from "../config/MarimoConfigurationService.ts";
import { showErrorAndPromptLogs } from "../lib/showErrorAndPromptLogs.ts";
import { VsCode } from "../platform/VsCode.ts";
import type { MarimoNotebookDocument } from "../schemas/MarimoNotebookDocument.ts";
import type { MarimoConfig } from "../types.ts";

/**
 * Generic configuration toggle function for marimo config options.
 * Creates a handler that shows a quick pick dialog with all available options.
 */
export const createConfigToggle = <T extends string>({
  notebook,
  configPath,
  settingName,
  pickerTitle,
  getCurrentValue,
  choices,
  getDisplayName,
}: {
  notebook: Option.Option<MarimoNotebookDocument>;
  configPath: string;
  settingName: string;
  pickerTitle: string;
  getCurrentValue: (config: MarimoConfig) => T;
  choices: ReadonlyArray<{
    label: string;
    detail: string;
    value: T;
  }>;
  getDisplayName: (value: T) => string;
}) =>
  Effect.gen(function* () {
    const code = yield* VsCode;
    const configService = yield* MarimoConfigurationService;

    if (Option.isNone(notebook)) {
      yield* showErrorAndPromptLogs(
        `Open a marimo notebook to configure ${settingName.toLowerCase()}.`,
      );
      return;
    }

    // Fetch current configuration
    const config = yield* configService.getConfig(notebook.value.id);
    const currentValue = getCurrentValue(config);

    // Show quick pick with all choices, marking current
    const choice = yield* code.window.showQuickPickItems(
      choices.map((c) => ({
        label: c.label,
        description: c.value === currentValue ? "$(check) Current" : undefined,
        detail: c.detail,
        value: c.value,
      })),
      { title: pickerTitle },
    );

    if (Option.isNone(choice)) {
      return; // User cancelled
    }

    const newValue = choice.value.value;

    if (newValue === currentValue) {
      yield* Effect.logInfo("Value unchanged");
      return;
    }

    // Update configuration
    yield* Effect.logInfo(`Updating ${configPath}`).pipe(
      Effect.annotateLogs({
        notebook: notebook.value.id,
        from: currentValue,
        to: newValue,
      }),
    );

    // Build nested config object from path (e.g., "runtime.on_cell_change" -> { runtime: { on_cell_change: value }})
    const pathParts = configPath.split(".");
    let partialConfig: Record<string, unknown> = {
      [pathParts[pathParts.length - 1]]: newValue,
    };
    for (let i = pathParts.length - 2; i >= 0; i--) {
      partialConfig = { [pathParts[i]]: partialConfig };
    }

    yield* configService.updateConfig(notebook.value.id, partialConfig);

    yield* code.window.showInformationMessage(
      `${settingName} set to ${getDisplayName(newValue)}.`,
    );
  }).pipe(
    Effect.tapErrorCause(Effect.logError),
    Effect.catchAllCause(() =>
      showErrorAndPromptLogs(`Could not update ${settingName.toLowerCase()}.`),
    ),
  );
