"use client";

import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
import { Select } from "antd";
import { ProviderType } from "@/types";
import { ModelParameter } from "@/lib/providers/types";
import { TranslationKey, useI18n } from "@/i18n";

/** Reorder items so they read column-first in a row-based CSS grid.
 *  e.g. [1,2,3,4,5,6,7,8] with 2 cols → [1,5,2,6,3,7,4,8] */
function reorderColumnFirst<T>(items: T[], cols: number): T[] {
  const rows = Math.ceil(items.length / cols);
  const result: T[] = [];
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      const idx = c * rows + r;
      if (idx < items.length) result.push(items[idx]);
    }
  }
  return result;
}

interface ModelParametersProps {
  modelId?: string;
  provider?: ProviderType;
  schema?: ModelParameter[];
  isLoading?: boolean;
  error?: string | null;
  parameters: Record<string, unknown>;
  onParametersChange: (parameters: Record<string, unknown>) => void;
  onExpandChange?: (expanded: boolean, parameterCount: number) => void;
  hiddenParameterNames?: string[];
  pruneHiddenParameters?: boolean;
}

/**
 * Collapsible parameter inputs for external provider models.
 * and renders appropriate inputs based on parameter types.
 */
function ModelParametersInner({
  modelId,
  schema = [],
  isLoading = false,
  error = null,
  parameters,
  onParametersChange,
  onExpandChange,
  hiddenParameterNames,
  pruneHiddenParameters = true,
}: ModelParametersProps) {
  const { t } = useI18n();
  const lastExpandCountRef = useRef<number | null>(null);

  const hiddenParameterSet = useMemo(
    () => new Set(hiddenParameterNames ?? []),
    [hiddenParameterNames]
  );
  const visibleSchema = useMemo(
    () => schema.filter((param) => !hiddenParameterSet.has(param.name)),
    [hiddenParameterSet, schema]
  );
  const visibleParameterCount = visibleSchema.length;

  // Remove parameters controlled by a higher-priority surface, such as the bottom composer.
  useEffect(() => {
    if (!pruneHiddenParameters || hiddenParameterSet.size === 0) return;
    const nextParameters = { ...parameters };
    let changed = false;
    for (const name of hiddenParameterSet) {
      if (Object.prototype.hasOwnProperty.call(nextParameters, name)) {
        delete nextParameters[name];
        changed = true;
      }
    }
    if (changed) {
      onParametersChange(nextParameters);
    }
  }, [hiddenParameterSet, onParametersChange, parameters, pruneHiddenParameters]);

  // Pre-populate visible schema defaults into parameters
  useEffect(() => {
    if (visibleSchema.length === 0) return;
    const baseParameters = { ...parameters };
    if (pruneHiddenParameters) {
      for (const name of hiddenParameterSet) {
        delete baseParameters[name];
      }
    }
    const defaults: Record<string, unknown> = {};
    let hasNewDefaults = false;
    for (const param of visibleSchema) {
      const currentValue = baseParameters[param.name];
      const enumValues = Array.isArray(param.enum) ? param.enum : [];
      const hasInvalidEnumValue =
        enumValues.length > 0 &&
        currentValue !== undefined &&
        !enumValues.some((item) => String(item) === String(currentValue));
      const defaultValue = param.default ?? enumValues[0];
      if ((currentValue === undefined || hasInvalidEnumValue) && defaultValue !== undefined) {
        defaults[param.name] = defaultValue;
        hasNewDefaults = true;
      }
    }
    if (hasNewDefaults) {
      onParametersChange({ ...baseParameters, ...defaults });
    }
  }, [visibleSchema, parameters, onParametersChange, hiddenParameterSet, pruneHiddenParameters]);

  // Notify parent to resize node when schema loads
  useEffect(() => {
    if (visibleParameterCount === 0) {
      lastExpandCountRef.current = null;
      return;
    }
    if (lastExpandCountRef.current === visibleParameterCount) return;
    lastExpandCountRef.current = visibleParameterCount;
    if (visibleParameterCount > 0 && onExpandChange) {
      onExpandChange(true, visibleParameterCount);
    }
  }, [visibleParameterCount, onExpandChange]);

  const handleParameterChange = useCallback(
    (name: string, value: unknown) => {
      // Create new parameters object with updated value
      const newParams = { ...parameters };

      // If value is empty/undefined, remove the parameter
      if (value === "" || value === undefined || value === null) {
        delete newParams[name];
      } else {
        newParams[name] = value;
      }

      onParametersChange(newParams);
    },
    [parameters, onParametersChange]
  );

  const sortedSchema = useMemo(() => {
    return [...visibleSchema].sort((a, b) => {
      // Sort order: dropdowns first, then numbers, then strings, then checkboxes last
      const typeOrder = (p: ModelParameter) => {
        if (p.options && p.options.length > 0) return 0; // dropdowns first
        if (p.type === "number" || p.type === "integer") return 1;
        if (p.type === "boolean") return 3; // checkboxes last
        return 2; // string and other
      };
      return typeOrder(a) - typeOrder(b);
    });
  }, [visibleSchema]);

  const useGrid = sortedSchema.length > 4;
  const gridRef = useRef<HTMLDivElement>(null);
  const [colCount, setColCount] = useState(1);

  useEffect(() => {
    const el = gridRef.current;
    if (!el || !useGrid) { setColCount(1); return; }
    let rafId: number;
    const observer = new ResizeObserver(() => {
      cancelAnimationFrame(rafId);
      rafId = requestAnimationFrame(() => {
        const cols = getComputedStyle(el).gridTemplateColumns.split(" ").length;
        setColCount(prev => prev === cols ? prev : cols);
      });
    });
    observer.observe(el);
    return () => {
      cancelAnimationFrame(rafId);
      observer.disconnect();
    };
  }, [useGrid]);

  const displaySchema = useMemo(() => {
    return useGrid && colCount > 1
      ? reorderColumnFirst(sortedSchema, colCount)
      : sortedSchema;
  }, [sortedSchema, useGrid, colCount]);

  // Don't render if no model selected
  if (!modelId) {
    return null;
  }

  // Don't render if no schema available and not loading
  if (!isLoading && visibleSchema.length === 0 && !error) {
    return null;
  }

  return (
    <div className="shrink-0">
      {error ? (
        <span className="text-[9px] text-red-400">{error}</span>
      ) : isLoading ? (
        <span className="text-[9px] text-neutral-500">{t("node.loadingParameters")}</span>
      ) : visibleSchema.length === 0 ? (
        <span className="text-[9px] text-neutral-500">{t("node.noParameters")}</span>
      ) : (
        <div
          ref={gridRef}
          className={useGrid
            ? "grid grid-cols-[repeat(auto-fill,minmax(min(180px,100%),1fr))] max-w-[420px] gap-x-6 gap-y-1.5"
            : "space-y-1.5 max-w-[280px]"
          }
        >
          {displaySchema.map((param) => (
            <ParameterInput
              key={param.name}
              param={param}
              name={param.name}
              value={parameters[param.name]}
              onChange={handleParameterChange}
              t={t}
            />
          ))}
        </div>
      )}
    </div>
  );
}

interface ParameterInputProps {
  param: ModelParameter;
  name: string;
  value: unknown;
  onChange: (name: string, value: unknown) => void;
  t: (key: TranslationKey, vars?: Record<string, string | number>) => string;
}

/**
 * Individual parameter input based on type.
 * Text and number inputs use local state during editing to prevent
 * cursor-jump issues caused by React Flow re-renders on store updates.
 */
function ParameterInputInner({ param, name, value, onChange, t }: ParameterInputProps) {
  // Stable callback that passes name along with value
  const handleChange = useCallback((value: unknown) => {
    onChange(name, value);
  }, [name, onChange]);
  const labelMap: Record<string, TranslationKey> = {
    size: "node.size",
    quality: "node.quality",
    voice: "node.voice",
    model: "node.model",
    aspect_ratio: "node.aspectRatio",
    resolution: "node.resolution",
    duration: "node.duration",
    duration_seconds: "node.duration",
    durationSeconds: "node.duration",
    sound: "node.sound",
    generate_audio: "node.sound",
    enable_audio: "node.sound",
    with_audio: "node.sound",
    audio_enabled: "node.sound",
  };
  const displayName = param.label ?? (labelMap[param.name]
    ? t(labelMap[param.name])
    : param.name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()));

  // Local state for text/number inputs to prevent cursor jumping
  const [localValue, setLocalValue] = useState<string>(() => {
    if (value === undefined || value === null) return "";
    return String(value);
  });
  const isFocusedRef = useRef(false);

  // Sync from store when not focused (external changes)
  useEffect(() => {
    if (!isFocusedRef.current) {
      setLocalValue(value === undefined || value === null ? "" : String(value));
    }
  }, [value]);

  // Determine input type and render accordingly
  if (param.options && param.options.length > 0) {
    const selectOptions = param.options.map((option, index) => ({
      value: String(index),
      label: option.label,
      rawValue: option.value,
    }));
    const selectedOption = selectOptions.find((option) =>
      Object.is(option.rawValue, value) || String(option.rawValue) === String(value ?? "")
    );

    // Render label/value option pairs from the model schema.
    return (
      <div className="flex items-center gap-2">
        <label
          className="text-[11px] text-neutral-400 shrink-0"
          title={param.description || undefined}
        >
          {displayName}
        </label>
        <Select
          value={selectedOption?.value}
          onChange={(optionKey) => {
            const option = selectOptions.find((item) => item.value === optionKey);
            handleChange(option?.rawValue);
          }}
          options={selectOptions.map((option) => ({
            value: option.value,
            label: option.label,
          }))}
          size="small"
          popupMatchSelectWidth={false}
          className="nodrag nopan flex-1 min-w-0 text-[11px] [&_.ant-select-selection-item]:text-[11px] [&_.ant-select-selector]:!text-[11px]"
          classNames={{
            popup: {
              root: "nodrag nopan [&_.ant-select-item-option-content]:text-[11px]",
            },
          }}
        />
      </div>
    );
  }

  if (param.type === "boolean") {
    // Use schema default when value not explicitly set
    const effectiveValue = value !== undefined ? Boolean(value) : Boolean(param.default);

    // Boolean: render as checkbox
    return (
      <label
        className="flex items-center gap-1.5 text-[11px] text-neutral-300 cursor-pointer"
        title={param.description || undefined}
      >
        <input
          type="checkbox"
          checked={effectiveValue}
          onChange={(e) => handleChange(e.target.checked)}
          className="nodrag nopan w-3 h-3 rounded bg-[#1a1a1a] text-neutral-600 focus:ring-1 focus:ring-neutral-600 focus:ring-offset-0"
        />
        <span>{displayName}</span>
      </label>
    );
  }

  if (param.type === "number" || param.type === "integer") {
    const hasMin = param.minimum !== undefined;
    const hasMax = param.maximum !== undefined;

    // Validate current value against constraints
    let validationError: string | null = null;
    if (localValue !== "" && !isNaN(Number(localValue))) {
      const num = Number(localValue);
      if (hasMin && num < param.minimum!) {
        validationError = `Min: ${param.minimum}`;
      } else if (hasMax && num > param.maximum!) {
        validationError = `Max: ${param.maximum}`;
      } else if (param.type === "integer" && !Number.isInteger(num)) {
        validationError = "Must be integer";
      }
    }

    return (
      <div className="flex flex-col gap-0.5">
        <div className="flex items-center gap-2">
          <label
            className="text-[11px] text-neutral-400 shrink-0 flex items-center gap-1"
            title={param.description || undefined}
          >
            {displayName}
            {hasMin && hasMax && (
              <span className="text-neutral-500 text-[9px]">
                ({param.minimum}-{param.maximum})
              </span>
            )}
          </label>
          <input
            type="number"
            value={localValue}
            min={param.minimum}
            max={param.maximum}
            step={param.type === "integer" ? 1 : 0.1}
            onFocus={() => { isFocusedRef.current = true; }}
            onChange={(e) => {
              setLocalValue(e.target.value);
            }}
            onBlur={() => {
              isFocusedRef.current = false;
              if (localValue === "") {
                handleChange(undefined);
              } else {
                const num = param.type === "integer" ? parseInt(localValue, 10) : parseFloat(localValue);
                handleChange(isNaN(num) ? undefined : num);
              }
            }}
            placeholder={param.default !== undefined ? `${param.default}` : undefined}
            className={`nodrag nopan flex-1 min-w-0 text-[11px] py-1 px-2 rounded-md bg-[#1a1a1a] focus:outline-none focus:ring-1 text-white placeholder:text-neutral-500 ${
              validationError
                ? "ring-1 ring-red-500"
                : "focus:ring-neutral-600"
            }`}
          />
        </div>
        {validationError && (
          <span className="text-[9px] text-red-400">{validationError}</span>
        )}
      </div>
    );
  }

  // Skip array type for now (complex)
  if (param.type === "array") {
    return null;
  }

  // Default: string input — uses local state, syncs to store on blur
  return (
    <div className="flex items-center gap-2">
      <label
        className="text-[11px] text-neutral-400 shrink-0"
        title={param.description || undefined}
      >
        {displayName}
      </label>
      <input
        type="text"
        value={localValue}
        onFocus={() => { isFocusedRef.current = true; }}
        onChange={(e) => {
          setLocalValue(e.target.value);
        }}
        onBlur={() => {
          isFocusedRef.current = false;
          handleChange(localValue || undefined);
        }}
        placeholder={param.default !== undefined ? `${param.default}` : undefined}
        className="nodrag nopan flex-1 min-w-0 text-[11px] py-1 px-2 rounded-md bg-[#1a1a1a] focus:outline-none focus:ring-1 focus:ring-neutral-600 text-white placeholder:text-neutral-500"
      />
    </div>
  );
}

// Memoized exports to prevent unnecessary re-renders
export const ModelParameters = React.memo(ModelParametersInner);
const ParameterInput = React.memo(ParameterInputInner);
