import { useState, useEffect, useCallback, useMemo } from "react";
import { Code2, Save, ChevronRight, ChevronDown } from "lucide-react";
import { api } from "../../services/api";

export enum GffFieldType {
  Byte = 0, Char = 1, Word = 2, Short = 3, Dword = 4, Int = 5,
  Dword64 = 6, Int64 = 7, Float = 8, Double = 9,
  CExoString = 10, ResRef = 11, CExoLocString = 12, Void = 13,
  Struct = 14, List = 15,
}

export interface GffFieldSchema {
  label: string;
  displayName: string;
  type: GffFieldType;
  description?: string;
  category?: string;
  editable?: boolean;
  hidden?: boolean;
  fields?: GffFieldSchema[];
}

export interface GffTypeSchema {
  fileType: string;
  displayName: string;
  categories: string[];
  fields: GffFieldSchema[];
}

function gffTypeFromPath(filePath: string): string | null {
  const match = filePath.match(/\.(\w+)\.json$/);
  return match ? match[1] : null;
}

function getFieldValue(data: Record<string, unknown>, label: string): unknown {
  const field = data[label];
  if (field && typeof field === "object" && "value" in (field as Record<string, unknown>)) {
    return (field as Record<string, unknown>).value;
  }
  return field;
}

function getLocStringText(value: unknown): string {
  if (value == null) return "";
  if (typeof value === "string") return value;
  if (typeof value === "object") {
    const v = value as Record<string, unknown>;
    if (typeof v["0"] === "string") return v["0"];
    for (const [key, val] of Object.entries(v)) {
      if (key === "id") continue;
      if (/^\d+$/.test(key) && typeof val === "string") return val;
    }
    if (v.strings && typeof v.strings === "object") {
      const strings = v.strings as Record<string, string>;
      return strings["0"] ?? Object.values(strings)[0] ?? "";
    }
    if (v.value && typeof v.value === "object") {
      return getLocStringText(v.value);
    }
    if (typeof v.id === "number") {
      const tlkRow = v.id >= 16777216 ? v.id - 16777216 : v.id;
      return `(TLK #${tlkRow})`;
    }
  }
  return String(value);
}

function setFieldValue(data: Record<string, unknown>, label: string, newValue: unknown): Record<string, unknown> {
  const updated = { ...data };
  const existing = data[label];
  if (existing && typeof existing === "object" && "value" in (existing as Record<string, unknown>)) {
    updated[label] = { ...(existing as Record<string, unknown>), value: newValue };
  } else {
    updated[label] = newValue;
  }
  return updated;
}

function setLocStringValue(data: Record<string, unknown>, label: string, text: string): Record<string, unknown> {
  const updated = { ...data };
  const existing = data[label];
  if (existing && typeof existing === "object") {
    const ex = existing as Record<string, unknown>;
    if ("value" in ex && ex.value && typeof ex.value === "object") {
      const inner = ex.value as Record<string, unknown>;
      if (typeof inner["0"] === "string") {
        updated[label] = { ...ex, value: { ...inner, "0": text } };
      } else {
        updated[label] = { ...ex, value: { ...inner, strings: { ...((inner.strings as object) ?? {}), "0": text } } };
      }
    } else if ("strings" in ex) {
      updated[label] = { ...ex, strings: { ...((ex.strings as object) ?? {}), "0": text } };
    } else {
      updated[label] = { ...ex, value: { strings: { "0": text } } };
    }
  } else {
    updated[label] = { type: "cexolocstring", value: { "0": text } };
  }
  return updated;
}

function isNumericType(type: GffFieldType): boolean {
  return type <= GffFieldType.Double;
}

const fieldRow: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  gap: "0.75rem",
  padding: "0.5rem 0",
};

const fieldLabel: React.CSSProperties = {
  width: "11rem",
  flexShrink: 0,
  fontSize: "var(--text-sm)",
  color: "var(--forge-text-secondary)",
};

const fieldInput: React.CSSProperties = {
  flex: 1,
  backgroundColor: "var(--forge-bg)",
  border: "1px solid var(--forge-border)",
  borderRadius: "0.375rem",
  padding: "0.5rem 0.75rem",
  fontSize: "var(--text-sm)",
  color: "var(--forge-text)",
  fontFamily: "inherit",
};

interface FieldRendererProps {
  field: GffFieldSchema;
  value: unknown;
  onChange: (value: unknown) => void;
  onLocStringChange?: (text: string) => void;
}

function FieldRenderer({ field, value, onChange, onLocStringChange }: FieldRendererProps) {
  if (field.hidden) return null;
  const isReadonly = field.editable === false;

  if (field.type === GffFieldType.Void) {
    const hex = typeof value === "string" ? value : "";
    return (
      <div style={{ ...fieldRow, alignItems: "flex-start" }}>
        <label style={{ ...fieldLabel, paddingTop: "0.5rem" }}>{field.displayName}</label>
        <code style={{ flex: 1, borderRadius: "0.375rem", padding: "0.5rem 0.75rem", fontSize: "var(--text-xs)", wordBreak: "break-all", backgroundColor: "var(--forge-bg)", color: "var(--forge-text-secondary)" }}>
          {hex || "(empty)"}
        </code>
      </div>
    );
  }

  if (field.type === GffFieldType.CExoLocString) {
    return <LocStringField field={field} value={value} isReadonly={isReadonly} onLocStringChange={onLocStringChange} />;
  }

  if (field.type === GffFieldType.List) {
    const list = Array.isArray(value) ? value : [];
    return <ListField field={field} items={list} />;
  }

  if (field.type === GffFieldType.Struct) {
    return <StructField field={field} value={value as Record<string, unknown> | undefined} />;
  }

  if (isNumericType(field.type)) {
    const num = typeof value === "number" ? value : 0;
    const isFloat = field.type === GffFieldType.Float || field.type === GffFieldType.Double;
    return (
      <div style={fieldRow}>
        <label style={fieldLabel}>{field.displayName}</label>
        <input type="number" value={num} readOnly={isReadonly} step={isFloat ? "0.01" : "1"} onChange={(e) => onChange(isFloat ? parseFloat(e.target.value) : parseInt(e.target.value, 10))} style={{ ...fieldInput, flex: "none", width: "8rem" }} />
      </div>
    );
  }

  if (field.type === GffFieldType.ResRef) {
    const str = typeof value === "string" ? value : "";
    const valid = str.length <= 16;
    return (
      <div style={fieldRow}>
        <label style={fieldLabel}>{field.displayName}</label>
        <div style={{ display: "flex", flex: 1, alignItems: "center", gap: "0.5rem" }}>
          <input type="text" value={str} readOnly={isReadonly} maxLength={16} onChange={(e) => onChange(e.target.value)} style={{ ...fieldInput, fontFamily: "var(--font-mono)", borderColor: valid ? "var(--forge-border)" : "var(--forge-danger)" }} />
          <span style={{ fontSize: "var(--text-xs)", color: valid ? "var(--forge-text-secondary)" : "var(--forge-danger)", flexShrink: 0 }}>{str.length}/16</span>
        </div>
      </div>
    );
  }

  const str = typeof value === "string" ? value : String(value ?? "");
  return (
    <div style={fieldRow}>
      <label style={fieldLabel}>{field.displayName}</label>
      <input type="text" value={str} readOnly={isReadonly} onChange={(e) => onChange(e.target.value)} style={fieldInput} />
    </div>
  );
}

function LocStringField({ field, value, isReadonly, onLocStringChange }: { field: GffFieldSchema; value: unknown; isReadonly: boolean; onLocStringChange?: (text: string) => void }) {
  const text = getLocStringText(value);
  const isTlkRef = text.startsWith("(TLK #");
  const [resolved, setResolved] = useState<string | null>(null);

  useEffect(() => {
    if (!isTlkRef || !value || typeof value !== "object") return;
    const v = value as Record<string, unknown>;
    const id = typeof v.id === "number" ? v.id : undefined;
    if (id === undefined) return;
    api.editor.tlkLookup?.(id).then((r) => { if (r) setResolved(r); }).catch(() => {});
  }, [value, isTlkRef]);

  const displayText = resolved ?? text;

  return (
    <div style={{ ...fieldRow, alignItems: "flex-start" }}>
      <label style={{ ...fieldLabel, paddingTop: "0.5rem" }}>{field.displayName}</label>
      <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: "0.25rem" }}>
        <input type="text" value={displayText} readOnly={isReadonly || isTlkRef} onChange={(e) => onLocStringChange?.(e.target.value)} style={{ ...fieldInput, ...(isTlkRef && !resolved ? { fontStyle: "italic", color: "var(--forge-text-secondary)" } : {}) }} />
        {isTlkRef && (
          <span style={{ fontSize: "var(--text-xs)", color: "var(--forge-text-secondary)" }}>
            {resolved ? text : "TLK reference — name stored in talk table"}
          </span>
        )}
      </div>
    </div>
  );
}

function ListField({ field, items }: { field: GffFieldSchema; items: unknown[] }) {
  const [expanded, setExpanded] = useState(false);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: "0.25rem" }}>
      <button onClick={() => setExpanded(!expanded)} style={{ display: "flex", alignItems: "center", gap: "0.5rem", fontSize: "var(--text-sm)", color: "var(--forge-text-secondary)", background: "none", border: "none", cursor: "pointer", padding: "0.25rem 0", textAlign: "left" }}>
        {expanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
        <span style={{ color: "var(--forge-text)" }}>{field.displayName}</span>
        <span style={{ fontSize: "var(--text-xs)", backgroundColor: "var(--forge-surface-raised)", borderRadius: "0.25rem", padding: "0.125rem 0.5rem" }}>{items.length} {items.length === 1 ? "item" : "items"}</span>
      </button>
      {expanded && (
        <div style={{ marginLeft: "1rem", marginTop: "0.25rem", paddingLeft: "1rem", borderLeft: "2px solid var(--forge-border)", display: "flex", flexDirection: "column", gap: "0.5rem" }}>
          {items.map((item, i) => (
            <div key={i} style={{ borderRadius: "0.375rem", padding: "0.5rem 0.75rem", backgroundColor: "var(--forge-bg)" }}>
              <div style={{ fontSize: "var(--text-xs)", fontWeight: 500, color: "var(--forge-text-secondary)", marginBottom: "0.25rem" }}>[{i}]</div>
              {typeof item === "object" && item !== null ? (
                <pre style={{ overflow: "auto", fontSize: "var(--text-xs)", color: "var(--forge-text)", margin: 0 }}>{JSON.stringify(item, null, 2)}</pre>
              ) : (
                <span style={{ fontSize: "var(--text-sm)", color: "var(--forge-text)" }}>{String(item)}</span>
              )}
            </div>
          ))}
          {items.length === 0 && <div style={{ padding: "0.25rem 0", fontSize: "var(--text-xs)", color: "var(--forge-text-secondary)" }}>(empty list)</div>}
        </div>
      )}
    </div>
  );
}

function StructField({ field, value }: { field: GffFieldSchema; value?: Record<string, unknown> }) {
  const [expanded, setExpanded] = useState(false);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: "0.25rem" }}>
      <button onClick={() => setExpanded(!expanded)} style={{ display: "flex", alignItems: "center", gap: "0.5rem", fontSize: "var(--text-sm)", color: "var(--forge-text-secondary)", background: "none", border: "none", cursor: "pointer", padding: "0.25rem 0", textAlign: "left" }}>
        {expanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
        <span style={{ color: "var(--forge-text)" }}>{field.displayName}</span>
      </button>
      {expanded && value && (
        <div style={{ marginLeft: "1rem", paddingLeft: "1rem", borderLeft: "2px solid var(--forge-border)" }}>
          <pre style={{ overflow: "auto", fontSize: "var(--text-xs)", color: "var(--forge-text)", margin: 0 }}>{JSON.stringify(value, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

interface GffEditorProps {
  repo: string;
  filePath: string;
  content: string;
  onSave?: (content: string) => void;
  onSwitchToRaw?: () => void;
  fieldOverrides?: Map<string, (props: FieldOverrideProps) => React.ReactNode>;
  headerSlot?: React.ReactNode;
}

export interface FieldOverrideProps {
  field: GffFieldSchema;
  value: unknown;
  data: Record<string, unknown>;
  onChange: (label: string, value: unknown) => void;
}

export function GffEditor({ repo, filePath, content, onSave, onSwitchToRaw, fieldOverrides, headerSlot }: GffEditorProps) {
  const [schema, setSchema] = useState<GffTypeSchema | null>(null);
  const [data, setData] = useState<Record<string, unknown>>({});
  const [activeCategory, setActiveCategory] = useState<string>("");
  const [dirty, setDirty] = useState(false);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const gffType = useMemo(() => gffTypeFromPath(filePath), [filePath]);

  useEffect(() => { try { setData(JSON.parse(content)); } catch { setError("Failed to parse JSON content"); } }, [content]);

  useEffect(() => {
    if (!gffType) return;
    api.editor.gffSchema(gffType).then((s) => { setSchema(s); if (s.categories.length > 0) setActiveCategory(s.categories[0]); }).catch(() => setError(`Failed to load schema for .${gffType}`));
  }, [gffType]);

  const handleFieldChange = useCallback((label: string, value: unknown) => { setData((prev) => { setDirty(true); return setFieldValue(prev, label, value); }); }, []);
  const handleLocStringChange = useCallback((label: string, text: string) => { setData((prev) => { setDirty(true); return setLocStringValue(prev, label, text); }); }, []);

  const handleSave = useCallback(async () => {
    setSaving(true);
    try { const newContent = JSON.stringify(data, null, 4) + "\n"; await api.editor.writeFile(repo, filePath, newContent); setDirty(false); onSave?.(newContent); }
    catch (err) { setError(err instanceof Error ? err.message : "Save failed"); }
    finally { setSaving(false); }
  }, [data, repo, filePath, onSave]);

  const categoryFields = useMemo(() => schema ? schema.fields.filter((f) => f.category === activeCategory && !f.hidden) : [], [schema, activeCategory]);

  if (error && !schema) {
    return (
      <div style={{ display: "flex", height: "100%", alignItems: "center", justifyContent: "center" }}>
        <div style={{ textAlign: "center" }}>
          <p style={{ fontSize: "var(--text-sm)", color: "var(--forge-danger)" }}>{error}</p>
          {onSwitchToRaw && (
            <button onClick={onSwitchToRaw} style={{ marginTop: "0.75rem", borderRadius: "0.375rem", padding: "0.5rem 1rem", fontSize: "var(--text-sm)", backgroundColor: "var(--forge-surface)", color: "var(--forge-text)", border: "1px solid var(--forge-border)", cursor: "pointer" }}>
              Open as Raw JSON
            </button>
          )}
        </div>
      </div>
    );
  }

  if (!schema) {
    return (
      <div style={{ display: "flex", height: "100%", alignItems: "center", justifyContent: "center" }}>
        <p style={{ fontSize: "var(--text-sm)", color: "var(--forge-text-secondary)" }}>Loading schema...</p>
      </div>
    );
  }

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", backgroundColor: "var(--forge-bg)" }}>
      {/* Toolbar */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexShrink: 0, padding: "0.625rem 1.25rem", borderBottom: "1px solid var(--forge-border)", backgroundColor: "var(--forge-surface)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
          <span style={{ fontFamily: "var(--font-heading)", fontSize: "var(--text-lg)", fontWeight: 600, color: "var(--forge-text)" }}>{schema.displayName}</span>
          {dirty && <span style={{ fontSize: "var(--text-xs)", color: "var(--forge-accent)", fontWeight: 500 }}>unsaved</span>}
          {error && <span style={{ fontSize: "var(--text-xs)", color: "var(--forge-danger)" }}>{error}</span>}
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
          {onSwitchToRaw && (
            <button onClick={onSwitchToRaw} style={{ display: "inline-flex", alignItems: "center", gap: "0.375rem", padding: "0.375rem 0.875rem", borderRadius: "0.375rem", border: "1px solid var(--forge-border)", backgroundColor: "transparent", color: "var(--forge-text-secondary)", fontSize: "var(--text-xs)", cursor: "pointer" }}>
              <Code2 size={13} /> Raw JSON
            </button>
          )}
          <button onClick={handleSave} disabled={!dirty || saving} style={{ display: "inline-flex", alignItems: "center", gap: "0.375rem", padding: "0.375rem 0.875rem", borderRadius: "0.375rem", border: "none", backgroundColor: "var(--forge-accent)", color: "var(--forge-accent-text)", fontSize: "var(--text-xs)", fontWeight: 600, cursor: dirty && !saving ? "pointer" : "not-allowed", opacity: dirty && !saving ? 1 : 0.4 }}>
            <Save size={13} /> {saving ? "Saving..." : "Save"}
          </button>
        </div>
      </div>

      {headerSlot}

      {/* Category tabs */}
      <div style={{ display: "flex", flexShrink: 0, overflowX: "auto", borderBottom: "1px solid var(--forge-border)", backgroundColor: "var(--forge-surface)" }}>
        {schema.categories.map((cat) => (
          <button key={cat} onClick={() => setActiveCategory(cat)} style={{ flexShrink: 0, padding: "0.625rem 1.25rem", fontSize: "var(--text-sm)", fontWeight: activeCategory === cat ? 600 : 400, color: activeCategory === cat ? "var(--forge-text)" : "var(--forge-text-secondary)", borderBottom: activeCategory === cat ? "2px solid var(--forge-accent)" : "2px solid transparent", background: "none", border: "none", borderBottomStyle: "solid", cursor: "pointer", transition: "color 150ms ease-out" }}>
            {cat}
          </button>
        ))}
      </div>

      {/* Fields */}
      <div style={{ flex: 1, overflowY: "auto", padding: "1.5rem" }}>
        <div style={{ maxWidth: "42rem", margin: "0 auto", display: "flex", flexDirection: "column", gap: "0.25rem" }}>
          {categoryFields.map((field) => {
            const override = fieldOverrides?.get(field.label);
            if (override) {
              return (
                <div key={field.label} style={{ padding: "0.5rem 0", borderBottom: "1px solid var(--forge-border)" }}>
                  {override({ field, value: getFieldValue(data, field.label), data, onChange: handleFieldChange })}
                </div>
              );
            }

            return (
              <div key={field.label} title={field.description} style={{ borderBottom: "1px solid var(--forge-border)" }}>
                <FieldRenderer
                  field={field}
                  value={getFieldValue(data, field.label)}
                  onChange={(val) => handleFieldChange(field.label, val)}
                  onLocStringChange={(text) => handleLocStringChange(field.label, text)}
                />
              </div>
            );
          })}
          {categoryFields.length === 0 && (
            <p style={{ padding: "2rem 0", textAlign: "center", fontSize: "var(--text-sm)", color: "var(--forge-text-secondary)" }}>No fields in this category</p>
          )}
        </div>
      </div>
    </div>
  );
}

export { gffTypeFromPath, getFieldValue, getLocStringText, setFieldValue, setLocStringValue };
