import { Effect, Layer, Option } from "effect";

import { CellMetadataUIBindingService } from "../notebook/CellMetadataUIBindingService.ts";
import { DatasourcesService } from "../panel/datasources/DatasourcesService.ts";
import { Constants } from "../platform/Constants.ts";
import { VsCode } from "../platform/VsCode.ts";
import {
  type MarimoCellMetadata,
  SqlCellProjection,
} from "../schemas/Models.gen.ts";

const DEFAULT_SQL_METADATA = SqlCellProjection.make();
export const DEFAULT_SQL_ENGINE = DEFAULT_SQL_METADATA.engine;
const DEFAULT_LABEL = "duckdb (In-Memory)";

/**
 * Layer that registers all cell metadata UI bindings.
 *
 * Currently includes:
 * - SQL dataframeName: Edit the dataframe variable name for SQL cells
 * - SQL showOutput: Toggle visibility of SQL query output
 * - SQL engine: Select the database connection/engine for SQL queries
 */
/**
 * Helper to update SQL metadata with defaults for all required fields
 */
function updateSqlMetadata(
  metadata: MarimoCellMetadata,
  updates: Partial<
    NonNullable<NonNullable<MarimoCellMetadata["sourceProjections"]>["sql"]>
  >,
): MarimoCellMetadata {
  return {
    ...metadata,
    sourceProjections: {
      ...metadata.sourceProjections,
      sql: {
        ...DEFAULT_SQL_METADATA,
        ...metadata.sourceProjections.sql,
        ...updates,
      },
    },
  };
}

export const CellMetadataBindingsLive = Layer.scopedDiscard(
  Effect.gen(function* () {
    const { LanguageId } = yield* Constants;
    const bindingService = yield* CellMetadataUIBindingService;
    const code = yield* VsCode;
    const datasources = yield* DatasourcesService;

    /**
     * SQL dataframeName binding
     * Allows editing the dataframe variable name that SQL query results are assigned to
     */
    yield* bindingService.registerBinding({
      id: "sql.dataframeName",
      type: "text",
      alignment: code.NotebookCellStatusBarAlignment.Left,

      shouldShow: (cell) => {
        return cell.document.languageId === LanguageId.Sql;
      },

      getValue: (metadata: MarimoCellMetadata) => {
        return metadata.sourceProjections?.sql?.dataframeName;
      },

      setValue: (metadata: MarimoCellMetadata, value: string | boolean) => {
        if (typeof value !== "string") {
          return metadata;
        }
        return updateSqlMetadata(metadata, { dataframeName: value });
      },

      getLabel: (value: string | boolean | undefined) => {
        const name = typeof value === "string" ? value : "unnamed";
        return `$(table) ${name}`;
      },

      getTooltip: (value: string | boolean | undefined) => {
        const name = typeof value === "string" ? value : "unnamed";
        return `SQL result dataframe: ${name} (click to edit)`;
      },

      inputPrompt: "Enter the variable name for the SQL query result",
      inputPlaceholder: "e.g., df, results, my_data",
      defaultValue: "df",

      validateInput: (value: string) => {
        // Python identifier validation
        if (!value) {
          return "Variable name cannot be empty";
        }
        if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
          return "Variable name must be a valid Python identifier (letters, numbers, underscores, must start with letter or underscore)";
        }
        // Reserved keywords check (basic list)
        const reservedKeywords = [
          "False",
          "None",
          "True",
          "and",
          "as",
          "assert",
          "async",
          "await",
          "break",
          "class",
          "continue",
          "def",
          "del",
          "elif",
          "else",
          "except",
          "finally",
          "for",
          "from",
          "global",
          "if",
          "import",
          "in",
          "is",
          "lambda",
          "nonlocal",
          "not",
          "or",
          "pass",
          "raise",
          "return",
          "try",
          "while",
          "with",
          "yield",
        ];
        if (reservedKeywords.includes(value)) {
          return `"${value}" is a Python reserved keyword`;
        }
        return undefined;
      },
    });

    /**
     * SQL showOutput binding
     * Toggle whether SQL query output is displayed
     */
    yield* bindingService.registerBinding({
      id: "sql.showOutput",
      type: "toggle",
      alignment: code.NotebookCellStatusBarAlignment.Left,

      shouldShow: (cell) => {
        return cell.document.languageId === LanguageId.Sql;
      },

      getValue: (metadata: MarimoCellMetadata) => {
        return metadata.sourceProjections?.sql?.showOutput ?? true;
      },

      setValue: (metadata: MarimoCellMetadata, value: string | boolean) => {
        if (typeof value !== "boolean") {
          return metadata;
        }
        return updateSqlMetadata(metadata, { showOutput: value });
      },

      getLabel: (value: string | boolean | undefined) => {
        const isVisible = value !== false;
        return isVisible ? "$(eye)" : "$(eye-closed)";
      },

      getTooltip: (value: string | boolean | undefined) => {
        const isVisible = value !== false;
        return isVisible
          ? "SQL output visible (click to hide)"
          : "SQL output hidden (click to show)";
      },
    });

    /**
     * SQL engine binding
     * Select the database connection/engine to use for SQL queries
     */
    yield* bindingService.registerBinding({
      id: "sql.engine",
      type: "option",
      alignment: code.NotebookCellStatusBarAlignment.Left,

      shouldShow: (cell) => {
        return cell.document.languageId === LanguageId.Sql;
      },

      getValue: (metadata: MarimoCellMetadata) => {
        return metadata.sourceProjections?.sql?.engine ?? DEFAULT_SQL_ENGINE;
      },

      setValue: (metadata: MarimoCellMetadata, value: string | boolean) => {
        if (typeof value !== "string") {
          return metadata;
        }
        return updateSqlMetadata(metadata, { engine: value });
      },

      getLabel: (value: string | boolean | undefined) => {
        const engine = typeof value === "string" ? value : DEFAULT_SQL_ENGINE;
        if (engine === DEFAULT_SQL_ENGINE) {
          // Pretty name for default engine
          return DEFAULT_LABEL;
        }
        return `$(database) ${engine}`;
      },

      getTooltip: (value: string | boolean | undefined) => {
        const engine = typeof value === "string" ? value : DEFAULT_SQL_ENGINE;
        if (engine === DEFAULT_SQL_ENGINE) {
          // Pretty name for default engine
          return DEFAULT_LABEL;
        }
        return `SQL engine: ${engine} (click to change)`;
      },

      inputPlaceholder: "Select a database connection",

      getOptions: (cell) => {
        return Effect.gen(function* () {
          const connectionsOption = yield* datasources.getConnections(
            cell.notebook.id,
          );

          // Always include duckdb as default
          const options: Array<{ label: string; value: string }> = [
            { label: DEFAULT_LABEL, value: DEFAULT_SQL_ENGINE },
          ];

          // Add available connections
          if (Option.isSome(connectionsOption)) {
            const connections = connectionsOption.value.connections;
            for (const [name, connection] of connections) {
              if (name === DEFAULT_SQL_ENGINE) {
                continue;
              }
              options.push({
                label: connection.display_name,
                value: name,
              });
            }
          }

          return options;
        });
      },
    });
  }),
);
