import { useState, useMemo } from 'react';
import { toast } from 'sonner';
import { type ZodType, z } from 'zod';
import { PRIMITIVE_REGISTRY } from '../modifiers/primitives/index.js';
import type { EffectPrimitiveNode, PrimitiveKind, EffectPrimitive } from '../modifiers/primitives/types.js';
import type { CustomModifierDescriptor } from '../modifiers/custom/types.js';
import {
  saveToCustomModifierLibrary,
  loadCustomModifierLibrary,
  type SavedCustomModifier,
} from '../modifiers/custom/library.js';
import { validateCustomDescriptor } from '../modifiers/custom/validate.js';
import { asCustomModifierId } from '../modifiers/custom/types.js';

interface Props {
  isOpen: boolean;
  onClose: () => void;
}

const CATEGORIES: Record<string, PrimitiveKind[]> = {
  State: [
    'seed-attribute',
    'add-to-attribute',
    'multiply-attribute',
    'add-direction',
    'set-capture-flag',
  ],
  Mechanic: [
    'absorb-damage-with-attribute',
    'reflect-damage',
    'modify-movement-range',
    'block-move-type',
    'override-promotion',
  ],
  Advanced: [
    'add-aura',
    'on-turn-start',
    'on-capture',
    'on-damaged',
    'conditional',
  ],
};

function generateId(): string {
  return `custom-${Math.random().toString(36).substring(2, 9)}`;
}

function makeBlankDescriptor(): CustomModifierDescriptor {
  return {
    type: 'data',
    id: asCustomModifierId(generateId()),
    name: 'New Custom Modifier',
    description: '',
    version: 1,
    primitives: [],
    targetAttrs: [], // Will be auto-computed based on primitives
    uiForm: 'primitive-composer',
    source: 'custom',
  };
}

/** Fallback to default params based on primitive schema */
function generateDefaultParams(schema: ZodType<unknown>): unknown {
  try {
    if (schema instanceof z.ZodDefault) {
      return (schema as unknown as { _def: { defaultValue: () => unknown } })._def.defaultValue();
    }
    if (schema instanceof z.ZodObject) {
      const shape = schema.shape as Record<string, ZodType<unknown>>;
      const result: Record<string, unknown> = {};
      for (const [key, subSchema] of Object.entries(shape)) {
        if (subSchema instanceof z.ZodDefault) {
          result[key] = (subSchema as unknown as { _def: { defaultValue: () => unknown } })._def.defaultValue();
        } else if (subSchema instanceof z.ZodNumber) {
          result[key] = 0;
        } else if (subSchema instanceof z.ZodString) {
          result[key] = '';
        } else if (subSchema instanceof z.ZodBoolean) {
          result[key] = false;
        } else if (subSchema instanceof z.ZodEnum) {
          result[key] = (subSchema as unknown as { _def: { values: unknown[] } })._def.values[0];
        } else if (subSchema instanceof z.ZodArray) {
          result[key] = [];
        } else if (subSchema instanceof z.ZodOptional || subSchema instanceof z.ZodNullable) {
          // Leave it undefined
        }
      }
      return result;
    }
    if (schema instanceof z.ZodNumber) return 0;
    if (schema instanceof z.ZodString) return '';
    if (schema instanceof z.ZodBoolean) return false;
    if (schema instanceof z.ZodEnum) return (schema as unknown as { _def: { values: unknown[] } })._def.values[0];
    if (schema instanceof z.ZodArray) return [];
  } catch (e) {
    console.error('Failed to generate default params', e);
  }
  return {}; // Fallback
}

export function CustomModifierEditor({ isOpen, onClose }: Props) {
  const [descriptor, setDescriptor] = useState<CustomModifierDescriptor>(makeBlankDescriptor());
  const [selectedIndex, setSelectedIndex] = useState<number | null>(null);

  // Library state for the "Load" dialog
  const [showLibrary, setShowLibrary] = useState(false);
  const [libraryItems, setLibraryItems] = useState<SavedCustomModifier[]>([]);

  // Open library
  const openLibrary = () => {
    setLibraryItems(loadCustomModifierLibrary());
    setShowLibrary(true);
  };

  const validationResult = useMemo(() => validateCustomDescriptor(descriptor), [descriptor]);

  const updateDescriptor = (updater: (prev: CustomModifierDescriptor) => CustomModifierDescriptor) => {
    setDescriptor((prev) => updater(prev));
  };

  const handleSave = () => {
    const res = saveToCustomModifierLibrary(descriptor);
    if (res.ok) {
      toast.success('Custom modifier saved to library');
    } else {
      toast.error(`Save failed: ${res.reason}`);
    }
  };

  if (!isOpen) return null;

  return (
    <div
      data-testid="custom-modifier-editor"
      className="fixed inset-0 z-[60] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4"
    >
      <div className="bg-white rounded-2xl shadow-2xl w-full max-w-[1400px] min-h-[85vh] max-h-[95vh] overflow-hidden flex flex-col border border-neutral-200">
        
        {/* Header */}
        <header className="flex items-center justify-between px-6 py-4 border-b border-neutral-200 shrink-0">
          <div className="flex-1 flex items-center gap-4">
            <h2 className="text-lg font-bold text-neutral-900 whitespace-nowrap">
              Primitive Composer
            </h2>
            <div className="flex flex-col gap-1 flex-1 max-w-md">
              <input
                type="text"
                value={descriptor.name}
                onChange={(e) => updateDescriptor((p) => ({ ...p, name: e.target.value }))}
                placeholder="Modifier Name"
                className="px-3 py-1 text-sm font-semibold border border-neutral-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
                maxLength={40}
              />
              <input
                type="text"
                value={descriptor.description}
                onChange={(e) => updateDescriptor((p) => ({ ...p, description: e.target.value }))}
                placeholder="Description (optional)"
                className="px-3 py-1 text-xs border border-neutral-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
                maxLength={200}
              />
            </div>
            <div className="text-xs text-neutral-500 font-mono self-end pb-1">
              ID: {descriptor.id}
            </div>
          </div>
          <div className="flex items-center gap-2">
            <button
              data-testid="custom-load"
              onClick={openLibrary}
              className="px-3 py-1.5 text-sm font-semibold text-neutral-700 bg-neutral-100 rounded hover:bg-neutral-200 transition-colors"
            >
              Load
            </button>
            <button
              data-testid="custom-save"
              onClick={handleSave}
              disabled={!validationResult.ok}
              className="px-3 py-1.5 text-sm font-semibold text-white bg-blue-600 rounded hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
            >
              Save to Library
            </button>
            <div className="w-px h-6 bg-neutral-300 mx-2" />
            <button
              onClick={onClose}
              className="p-2 text-neutral-500 hover:bg-neutral-100 rounded transition-colors"
              aria-label="Close"
            >
              ×
            </button>
          </div>
        </header>

        {/* Main Body (3 Columns) */}
        <div className="flex-1 flex min-h-0 overflow-hidden">
          
          {/* Left: Palette */}
          <aside className="w-64 border-r border-neutral-200 overflow-y-auto bg-neutral-50/50 flex flex-col p-4 gap-6 shrink-0">
            {Object.entries(CATEGORIES).map(([category, kinds]) => (
              <div key={category}>
                <h3 className="text-xs font-bold text-neutral-500 uppercase tracking-wider mb-3">
                  {category}
                </h3>
                <div className="flex flex-col gap-2">
                  {kinds.map((kind) => {
                    const primitive = PRIMITIVE_REGISTRY.get(kind);
                    if (!primitive) return null;
                    return (
                      <button
                        key={kind}
                        data-testid={`custom-primitive-palette-${kind}`}
                        className="text-left px-3 py-2 bg-white border border-neutral-200 rounded-lg shadow-sm hover:border-blue-300 hover:shadow transition-all group"
                        onClick={() => {
                          const newNode: EffectPrimitiveNode = {
                            kind,
                            params: generateDefaultParams(primitive.paramsSchema),
                          };
                          updateDescriptor((p) => ({
                            ...p,
                            primitives: [...p.primitives, newNode],
                          }));
                          setSelectedIndex(descriptor.primitives.length); // select the newly added node
                        }}
                      >
                        <div className="text-sm font-semibold text-neutral-800">
                          {primitive.label}
                        </div>
                        <div className="text-xs text-neutral-500 truncate mt-0.5" title={primitive.description}>
                          {primitive.description}
                        </div>
                      </button>
                    );
                  })}
                </div>
              </div>
            ))}
          </aside>

          {/* Center: Tree View */}
          <main className="flex-[2] border-r border-neutral-200 overflow-y-auto bg-white p-6 flex flex-col">
            <h3 className="text-sm font-bold text-neutral-800 mb-4 border-b border-neutral-100 pb-2">
              Effect Sequence ({descriptor.primitives.length})
            </h3>
            {descriptor.primitives.length === 0 ? (
              <div className="flex-1 flex items-center justify-center text-sm text-neutral-400 italic">
                Add primitives from the palette to build the modifier.
              </div>
            ) : (
              <div className="flex flex-col gap-2">
                {descriptor.primitives.map((node, i) => {
                  const isSelected = selectedIndex === i;
                  const primitive = PRIMITIVE_REGISTRY.get(node.kind);
                  return (
                    <div
                      key={i}
                      data-testid={`custom-primitive-node-${i}`}
                      onClick={() => setSelectedIndex(i)}
                      className={`
                        flex items-center justify-between p-3 rounded-lg border cursor-pointer transition-colors
                        ${isSelected 
                          ? 'border-blue-500 bg-blue-50 shadow-sm ring-1 ring-blue-500/20' 
                          : 'border-neutral-200 hover:border-neutral-300 bg-white hover:bg-neutral-50'}
                      `}
                    >
                      <div className="flex items-center gap-3 overflow-hidden">
                        <div className={`
                          flex items-center justify-center w-6 h-6 rounded-full text-xs font-bold shrink-0
                          ${isSelected ? 'bg-blue-200 text-blue-800' : 'bg-neutral-100 text-neutral-500'}
                        `}>
                          {i + 1}
                        </div>
                        <div className="min-w-0">
                          <div className={`text-sm font-semibold ${isSelected ? 'text-blue-900' : 'text-neutral-800'}`}>
                            {primitive?.label ?? node.kind}
                          </div>
                          <div className="text-xs text-neutral-500 truncate">
                            {JSON.stringify(node.params).substring(0, 60)}
                            {JSON.stringify(node.params).length > 60 ? '...' : ''}
                          </div>
                        </div>
                      </div>
                      <button
                        className="p-1.5 text-neutral-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors shrink-0"
                        onClick={(e) => {
                          e.stopPropagation();
                          updateDescriptor((p) => ({
                            ...p,
                            primitives: p.primitives.filter((_, idx) => idx !== i),
                          }));
                          if (selectedIndex === i) setSelectedIndex(null);
                          else if (selectedIndex && selectedIndex > i) setSelectedIndex(selectedIndex - 1);
                        }}
                        title="Remove primitive"
                      >
                        ×
                      </button>
                    </div>
                  );
                })}
              </div>
            )}
          </main>

          {/* Right: Inspector */}
          <aside 
            data-testid="custom-primitive-inspector"
            className="flex-[1.5] bg-neutral-50/30 overflow-y-auto p-6"
          >
            <h3 className="text-sm font-bold text-neutral-800 mb-4 border-b border-neutral-100 pb-2">
              Parameter Inspector
            </h3>
            {selectedIndex === null || !descriptor.primitives[selectedIndex] ? (
              <div className="flex items-center justify-center h-48 text-sm text-neutral-400 italic">
                Select a primitive to edit its parameters.
              </div>
            ) : (
              <PrimitiveInspector
                node={descriptor.primitives[selectedIndex]}
                primitive={PRIMITIVE_REGISTRY.get(descriptor.primitives[selectedIndex].kind)!}
                onChange={(newParams) => {
                  updateDescriptor((p) => {
                    const next = [...p.primitives];
                    next[selectedIndex] = { kind: next[selectedIndex]!.kind, params: newParams };
                    return { ...p, primitives: next };
                  });
                }}
              />
            )}
          </aside>
        </div>

        {/* Footer: Validation Status */}
        <footer className="px-6 py-3 border-t border-neutral-200 bg-neutral-50 shrink-0 flex items-center justify-between">
          <div className="flex items-center gap-2 text-sm">
            {validationResult.ok ? (
              <div className="flex items-center gap-1.5 text-emerald-600 font-medium">
                <span className="text-lg">✓</span> Valid Custom Descriptor
              </div>
            ) : (
              <div className="flex items-center gap-1.5 text-red-600 font-medium">
                <span className="text-lg">✕</span> {validationResult.errors.length} Error{validationResult.errors.length !== 1 ? 's' : ''}
              </div>
            )}
          </div>
          {!validationResult.ok && (
            <div className="text-xs text-red-500 text-right max-w-2xl truncate">
              {validationResult.errors.map(e => `[${e.path.join('.')}] ${e.message}`).join(' | ')}
            </div>
          )}
        </footer>
      </div>

      {/* Load Modal Overlay */}
      {showLibrary && (
        <div className="fixed inset-0 z-[70] bg-black/40 backdrop-blur-sm flex items-center justify-center p-4">
          <div className="bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[80vh] flex flex-col overflow-hidden">
            <header className="px-6 py-4 border-b border-neutral-200 flex items-center justify-between">
              <h3 className="text-lg font-bold text-neutral-900">Load from Library</h3>
              <button onClick={() => setShowLibrary(false)} className="text-neutral-500 hover:text-neutral-700">×</button>
            </header>
            <div className="flex-1 overflow-y-auto p-4">
              {libraryItems.length === 0 ? (
                <div className="text-center py-12 text-neutral-500">No custom modifiers saved yet.</div>
              ) : (
                <div className="flex flex-col gap-2">
                  {libraryItems.map(item => (
                    <div 
                      key={item.id}
                      className="flex items-center justify-between p-4 border border-neutral-200 rounded-lg hover:border-blue-400 cursor-pointer"
                      onClick={() => {
                        setDescriptor(item.descriptor);
                        setSelectedIndex(null);
                        setShowLibrary(false);
                      }}
                    >
                      <div>
                        <div className="font-semibold text-neutral-900">{item.descriptor.name}</div>
                        <div className="text-xs text-neutral-500 mt-1">{item.descriptor.primitives.length} primitives</div>
                      </div>
                      <div className="text-xs text-blue-600 font-medium bg-blue-50 px-2 py-1 rounded">Load</div>
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

/** 
 * Sub-component for rendering the parameter form based on Zod schema introspection.
 * Since fully parsing arbitrary Zod schemas into UI is complex, we use a hybrid approach:
 * basic types get inputs, complex types get a JSON textarea fallback.
 */
function PrimitiveInspector({ 
  node, 
  primitive,
  onChange 
}: { 
  node: EffectPrimitiveNode; 
  primitive?: EffectPrimitive;
  onChange: (params: unknown) => void;
}) {
  if (!primitive) {
    return <div className="text-red-500 text-sm">Unknown primitive kind: {node.kind}</div>;
  }

  // Generic fallback for complex schemas (nested arrays, etc)
  const isComplex = primitive.paramsSchema instanceof z.ZodObject === false;
  
  if (isComplex) {
    const isArray = primitive.paramsSchema instanceof z.ZodArray;
    return (
      <div className="flex flex-col gap-2">
        <label className="text-xs font-bold text-neutral-600 uppercase tracking-wide">
          {isArray ? 'List Parameters (JSON)' : 'Raw Parameters (JSON)'}
        </label>
        <textarea 
          className="w-full h-64 p-3 text-sm font-mono border border-neutral-300 rounded focus:ring-2 focus:ring-blue-500 focus:outline-none"
          value={JSON.stringify(node.params, null, 2)}
          onChange={(e) => {
            try {
              const val = JSON.parse(e.target.value);
              onChange(val);
            } catch {
              // Let them type invalid JSON temporarily — typing
              // states naturally pass through partial-parse states.
            }
          }}
        />
        <p className="text-xs text-neutral-500">Edit the JSON representation directly for complex parameters.</p>
      </div>
    );
  }

  // Reach into Zod internals to introspect the schema's shape and
  // narrow the param-type rendering. The shape of a ZodObject and the
  // _def of optional/default/enum nodes is internal API; we cast to a
  // narrow structural shape rather than `any` so the access points
  // are auditable. If Zod ever renames _def, fix the cast in one place.
  type ZodObjectInternal = { shape: Record<string, ZodType<unknown>> };
  type ZodWrappedDefInternal = {
    _def: { innerType?: ZodType<unknown>; schema?: ZodType<unknown> };
  };
  type ZodEnumDefInternal = { _def: { values: readonly string[] } };

  const shape = (primitive.paramsSchema as unknown as ZodObjectInternal).shape;
  const params = (node.params as Record<string, unknown>) || {};

  return (
    <div className="flex flex-col gap-5">
      {Object.entries(shape).map(([key, schema]) => {
        let type = 'string';
        let options: readonly string[] = [];

        // Basic introspection
        let currentSchema: ZodType<unknown> = schema;
        if (
          currentSchema instanceof z.ZodOptional ||
          currentSchema instanceof z.ZodNullable ||
          currentSchema instanceof z.ZodDefault
        ) {
          const def = (currentSchema as unknown as ZodWrappedDefInternal)._def;
          currentSchema = def.innerType ?? def.schema ?? currentSchema;
        }

        if (currentSchema instanceof z.ZodNumber) type = 'number';
        else if (currentSchema instanceof z.ZodBoolean) type = 'boolean';
        else if (currentSchema instanceof z.ZodEnum) {
          type = 'enum';
          options = (currentSchema as unknown as ZodEnumDefInternal)._def.values;
        }
        else if (currentSchema instanceof z.ZodArray) type = 'array';

        const value = params[key];

        return (
          <div key={key} className="flex flex-col gap-1.5">
            <label className="text-xs font-bold text-neutral-700">
              {key}
            </label>
            
            {type === 'number' && (
              <input 
                type="number" 
                value={Number(value) || 0}
                onChange={(e) => onChange({ ...params, [key]: Number(e.target.value) })}
                className="px-3 py-2 text-sm border border-neutral-300 rounded focus:ring-2 focus:ring-blue-500 focus:outline-none"
              />
            )}
            
            {type === 'string' && (
              <input 
                type="text" 
                value={String(value || '')}
                onChange={(e) => onChange({ ...params, [key]: e.target.value })}
                className="px-3 py-2 text-sm border border-neutral-300 rounded focus:ring-2 focus:ring-blue-500 focus:outline-none"
              />
            )}
            
            {type === 'boolean' && (
              <label className="flex items-center gap-2 cursor-pointer">
                <input 
                  type="checkbox" 
                  checked={Boolean(value)}
                  onChange={(e) => onChange({ ...params, [key]: e.target.checked })}
                  className="w-4 h-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
                />
                <span className="text-sm text-neutral-600">Enabled</span>
              </label>
            )}
            
            {type === 'enum' && (
              <select
                value={String(value || options[0])}
                onChange={(e) => onChange({ ...params, [key]: e.target.value })}
                className="px-3 py-2 text-sm border border-neutral-300 rounded focus:ring-2 focus:ring-blue-500 focus:outline-none bg-white"
              >
                {options.map(opt => (
                  <option key={opt} value={opt}>{opt}</option>
                ))}
              </select>
            )}
            
            {type === 'array' && (
              <div className="border border-neutral-200 rounded bg-white overflow-hidden flex flex-col">
                <textarea 
                  className="w-full h-32 p-2 text-xs font-mono border-0 focus:ring-0 resize-none"
                  value={JSON.stringify(value || [], null, 2)}
                  onChange={(e) => {
                    try {
                      const val = JSON.parse(e.target.value);
                      if (Array.isArray(val)) {
                        onChange({ ...params, [key]: val });
                      }
                    } catch {
                      // Partial-typing tolerance; commit on valid JSON only.
                    }
                  }}
                  placeholder="[ ... ]"
                />
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}
