import { useMemo, useRef } from "react";
import type { ApiSettings } from "../../types";
import { Button } from "@govoplan/core-webui";
import { PageTitle } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui";
import LockedVersionNotice from "./components/LockedVersionNotice";
import VersionLine from "./components/VersionLine";
import { ToggleSwitch } from "@govoplan/core-webui";
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
import { useCampaignDraftEditor } from "./hooks/useCampaignDraftEditor";
import { asRecord, isAuditLockedVersion, isRecord } from "./utils/campaignView";
import { getBool, getText, updateNested } from "./utils/draftEditor";
import FieldValueInput from "./components/FieldValueInput";
import { DismissibleAlert } from "@govoplan/core-webui";
import DataGrid, { DataGridEmptyAction, DataGridRowActions, type DataGridColumn } from "../../components/table/DataGrid";
import { fieldTypeOptions, humanizeFieldName, normalizeFieldType, type CampaignFieldDefinition } from "./utils/fieldDefinitions";
import { insertAfter, moveArrayItem } from "@govoplan/core-webui";
export default function CampaignFieldsPage({ settings, campaignId }: { settings: ApiSettings; campaignId: string }) {
  const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId);
  const fieldValueKeys = useRef<string[]>([]);

  const version = data.currentVersion;
  const locked = isAuditLockedVersion(version, data.campaign?.current_version_id);
  const { draft, setDraft, displayDraft, dirty, saveState, setSaveState, localError, setLocalError, markDirty, saveDraft } = useCampaignDraftEditor({
    settings,
    campaignId,
    version,
    locked,
    reload,
    setError,
    currentStep: "campaign-fields",
    unsavedTitle: "Unsaved fields",
    unsavedMessage: "Campaign fields have unsaved changes. Save them before leaving, or discard them and continue.",
    transformLoadedDraft: (loadedVersion, loadedDraft) => migrateFieldOverridePolicy(loadedDraft, asRecord(loadedVersion.editor_state)),
    onLoaded: (_loadedVersion, loadedDraft) => {
      fieldValueKeys.current = normalizeFields(loadedDraft.fields).map((field) => field.name);
    }
  });
  const fields = useMemo(() => normalizeFields(displayDraft.fields), [displayDraft.fields]);
  const globalValues = asRecord(displayDraft.global_values);
  const fieldNameWarning = useMemo(() => describeFieldNameProblem(fields), [fields]);
  const canSave = dirty && !locked && Boolean(draft) && !fieldNameWarning;


  function patchDraft(path: string[], value: unknown) {
    if (locked) return;
    setDraft((current) => updateNested(current ?? {}, path, value));
    markDirty();
  }

  function patchFields(nextFields: CampaignFieldDefinition[]) {
    patchDraft(["fields"], nextFields.map((field) => ({
      name: field.name,
      type: field.type || "string",
      label: field.label,
      required: field.required,
      can_override: field.can_override
    })));
  }

  function patchGlobalValues(nextValues: Record<string, unknown>) {
    patchDraft(["global_values"], nextValues);
  }


  function setField(index: number, patchValue: Partial<CampaignFieldDefinition>) {
    const nextFields = fields.map((field, currentIndex) => currentIndex === index ? { ...field, ...patchValue } : field);
    patchFields(nextFields);
  }

  function renameField(index: number, nextName: string) {
    const oldName = fields[index]?.name ?? "";
    const cleanedName = nextName.trim();
    const duplicate = Boolean(cleanedName) && fields.some((field, currentIndex) => currentIndex !== index && field.name === cleanedName);
    const valueKey = fieldValueKeys.current[index] ?? oldName;
    const nextFields = fields.map((field, currentIndex) => currentIndex === index ? { ...field, name: cleanedName } : field);
    const nextGlobalValues = { ...globalValues };

    if (!duplicate && cleanedName) {
      if (valueKey && valueKey !== cleanedName && Object.prototype.hasOwnProperty.call(nextGlobalValues, valueKey)) {
        nextGlobalValues[cleanedName] = nextGlobalValues[valueKey];
        delete nextGlobalValues[valueKey];
      }
      fieldValueKeys.current[index] = cleanedName;
    }

    setDraft((current) => {
      const nextDraft = updateNested(current ?? {}, ["fields"], nextFields);
      const nextWithGlobalValues = updateNested(nextDraft, ["global_values"], nextGlobalValues);
      if (duplicate || !cleanedName || !valueKey || valueKey === cleanedName) {
        return nextWithGlobalValues;
      }
      return migrateEntryFieldValues(nextWithGlobalValues, valueKey, cleanedName);
    });
    markDirty();
  }

  function addField(afterIndex = fields.length - 1) {
    const name = uniqueFieldName(fields);
    const nextField = { name, label: humanizeFieldName(name), type: "string", required: false, can_override: true };
    const nextFields = insertAfter(fields, afterIndex, nextField);
    fieldValueKeys.current = insertAfter(fieldValueKeys.current, afterIndex, name);
    const nextGlobalValues = { ...globalValues, [name]: "" };
    setDraft((current) => {
      const nextDraft = updateNested(current ?? {}, ["fields"], nextFields);
      return updateNested(nextDraft, ["global_values"], nextGlobalValues);
    });
    markDirty();
  }

  function moveField(index: number, targetIndex: number) {
    if (locked || index === targetIndex) return;
    fieldValueKeys.current = moveArrayItem(fieldValueKeys.current, index, targetIndex);
    patchFields(moveArrayItem(fields, index, targetIndex));
  }

  function deleteField(index: number) {
    const field = fields[index];
    const nextFields = fields.filter((_, currentIndex) => currentIndex !== index);
    fieldValueKeys.current = fieldValueKeys.current.filter((_, currentIndex) => currentIndex !== index);
    const nextGlobalValues = { ...globalValues };
    if (field?.name) {
      delete nextGlobalValues[field.name];
    }
    setDraft((current) => {
      const nextDraft = updateNested(current ?? {}, ["fields"], nextFields);
      return updateNested(nextDraft, ["global_values"], nextGlobalValues);
    });
    markDirty();
  }

  function setGlobalValue(key: string, value: unknown) {
    patchGlobalValues({ ...globalValues, [key]: value });
  }

  function setOverrideAllowed(index: number, allowed: boolean) {
    setField(index, { can_override: allowed });
  }

  async function saveFields(): Promise<boolean> {
    const fieldProblem = describeFieldNameProblem(fields);
    if (fieldProblem) {
      setLocalError(fieldProblem);
      setSaveState("Save blocked");
      return false;
    }
    return saveDraft("manual");
  }


  return (
    <div className="content-pad workspace-data-page">
      <div className="page-heading split workspace-heading">
        <div>
          <PageTitle loading={loading}>Fields</PageTitle>
          <VersionLine version={version} versions={data.versions} status={saveState} />
        </div>
        <div className="button-row compact-actions">
          <Button onClick={reload} disabled={loading}>Reload</Button>
          <Button variant="primary" onClick={saveFields} disabled={!canSave}>Save</Button>
        </div>
      </div>

      {error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
      {localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
      {fieldNameWarning && <DismissibleAlert tone="warning" resetKey={fieldNameWarning} floating>{fieldNameWarning}</DismissibleAlert>}
      {locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} currentVersionId={data.campaign?.current_version_id} reload={reload} message="This page is read-only for the selected version." />}

      <LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
        <>
          <Card title="Campaign fields">
            <div className="admin-table-surface campaign-fields-table-surface">
              <DataGrid
                id={`campaign-${campaignId}-fields`}
                rows={fields}
                columns={fieldColumns({ locked, fields, globalValues, renameField, setField, setGlobalValue, setOverrideAllowed, addField, moveField, deleteField })}
                getRowKey={(_field, index) => `field-row-${index}`}
                emptyText="No campaign fields configured yet."
                emptyAction={<DataGridEmptyAction onAdd={() => addField(-1)} disabled={locked} label="Add first field" />}
                className="field-editor-table-wrap field-editor-table"
              />
            </div>
          </Card>
        </>
      </LoadingFrame>
    </div>
  );
}

type FieldColumnContext = {
  locked: boolean;
  fields: CampaignFieldDefinition[];
  globalValues: Record<string, unknown>;
  renameField: (index: number, nextName: string) => void;
  setField: (index: number, patchValue: Partial<CampaignFieldDefinition>) => void;
  setGlobalValue: (key: string, value: unknown) => void;
  setOverrideAllowed: (index: number, allowed: boolean) => void;
  addField: (afterIndex?: number) => void;
  moveField: (index: number, targetIndex: number) => void;
  deleteField: (index: number) => void;
};

function fieldColumns({ locked, fields, globalValues, renameField, setField, setGlobalValue, setOverrideAllowed, addField, moveField, deleteField }: FieldColumnContext): DataGridColumn<CampaignFieldDefinition>[] {
  return [
    { id: "name", header: "Field ID", width: 190, resizable: true, sortable: true, filterable: true, sticky: "start", render: (field, index) => <input value={field.name} disabled={locked} placeholder="field_name" onChange={(event) => renameField(index, event.target.value)} />, value: (field) => field.name },
    { id: "label", header: "Label", width: 210, resizable: true, sortable: true, filterable: true, render: (field, index) => <input value={field.label} disabled={locked} placeholder="Display label" onChange={(event) => setField(index, { label: event.target.value })} />, value: (field) => field.label },
    { id: "type", header: "Type", width: 140, sortable: true, filterable: true, columnType: "from-list", list: { options: fieldTypeOptions.map((option) => ({ value: option, label: option })) }, render: (field, index) => <select value={field.type} disabled={locked} onChange={(event) => setField(index, { type: normalizeFieldType(event.target.value) })}>{fieldTypeOptions.map((option) => <option key={option} value={option}>{option}</option>)}</select>, value: (field) => field.type },
    { id: "required", header: "Required", width: 140, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "required", label: "Required" }, { value: "optional", label: "Optional" }] }, render: (field, index) => <ToggleSwitch label="Required" checked={field.required} disabled={locked} onChange={(checked) => setField(index, { required: checked })} />, value: (field) => field.required ? "required" : "optional" },
    { id: "global_value", header: "Global value", width: 220, resizable: true, filterable: true, render: (field) => <FieldValueInput fieldType={field.type} value={globalValues[field.name]} disabled={locked || !field.name} placeholder="Optional default" onChange={(value) => setGlobalValue(field.name, value)} />, value: (field) => String(globalValues[field.name] ?? "") },
    { id: "override", header: "Recipient override", width: 170, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "can override", label: "Can override" }, { value: "locked", label: "Global only" }] }, render: (field, index) => <ToggleSwitch label="Can override" checked={field.can_override} disabled={locked || !field.name} onChange={(checked) => setOverrideAllowed(index, checked)} />, value: (field) => field.can_override ? "can override" : "locked" },
    {
      id: "actions",
      header: "Actions",
      width: 180,
      sticky: "end",
      render: (_field, index) => (
        <DataGridRowActions
          disabled={locked}
          onAddBelow={() => addField(index)}
          onRemove={() => deleteField(index)}
          onMoveUp={index > 0 ? () => moveField(index, index - 1) : undefined}
          onMoveDown={index < fields.length - 1 ? () => moveField(index, index + 1) : undefined}
          addLabel="Add field below"
          removeLabel="Remove field"
          moveUpLabel="Move field up"
          moveDownLabel="Move field down"
        />
      )
    }
  ];
}

function normalizeFields(value: unknown): CampaignFieldDefinition[] {
  if (!Array.isArray(value)) return [];
  return value.filter(isRecord).map((field) => ({
    name: getText(field, "name"),
    label: getText(field, "label"),
    type: normalizeFieldType(getText(field, "type")),
    required: getBool(field, "required"),
    can_override: getBool(field, "can_override", true)
  }));
}


function migrateEntryFieldValues(draft: Record<string, unknown>, oldName: string, newName: string): Record<string, unknown> {
  const entries = asRecord(draft.entries);
  const inlineEntries = Array.isArray(entries.inline) ? entries.inline : [];
  if (inlineEntries.length === 0) return draft;

  const nextInlineEntries = inlineEntries.map((entry) => {
    if (!isRecord(entry)) return entry;
    const fields = asRecord(entry.fields);
    if (!Object.prototype.hasOwnProperty.call(fields, oldName)) return entry;

    const nextFields = { ...fields };
    if (!Object.prototype.hasOwnProperty.call(nextFields, newName)) {
      nextFields[newName] = nextFields[oldName];
    }
    delete nextFields[oldName];
    return { ...entry, fields: nextFields };
  });

  return updateNested(draft, ["entries", "inline"], nextInlineEntries);
}

function migrateFieldOverridePolicy(draft: Record<string, unknown>, editorState: Record<string, unknown>): Record<string, unknown> {
  const overridePolicy = asRecord(editorState.field_overrides);
  if (Object.keys(overridePolicy).length === 0) return draft;

  const fields = normalizeFields(draft.fields).map((field) => {
    if (!field.name || !Object.prototype.hasOwnProperty.call(overridePolicy, field.name)) return field;
    return { ...field, can_override: getBool(overridePolicy, field.name, true) };
  });

  return updateNested(draft, ["fields"], fields);
}

function describeFieldNameProblem(fields: CampaignFieldDefinition[]): string {
  const names = fields.map((field) => field.name.trim());
  if (names.some((name) => !name)) {
    return "Field IDs must not be empty before saving.";
  }

  const seen = new Set<string>();
  const duplicates = new Set<string>();
  for (const name of names) {
    if (seen.has(name)) duplicates.add(name);
    seen.add(name);
  }

  if (duplicates.size === 0) return "";
  return `Duplicate field ID${duplicates.size === 1 ? "" : "s"}: ${[...duplicates].sort().join(", ")}. Field IDs must be unique before saving.`;
}

function uniqueFieldName(fields: CampaignFieldDefinition[]): string {
  const existing = new Set(fields.map((field) => field.name));
  let counter = fields.length + 1;
  let name = `field_${counter}`;
  while (existing.has(name)) {
    counter += 1;
    name = `field_${counter}`;
  }
  return name;
}
