/* ============================================================
   ConfigEditor — femtoAI EVK system_config.json editor
   Load from device (CMD:READ) or from a local file, edit fields
   by type, write to SD card. Exported to window.ConfigEditor.
   ============================================================ */
const { useState, useRef, useCallback, useEffect } = React;

/* ---- Helpers ------------------------------------------------ */
function humanLabel(key) {
  return key.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
}
function isFemtoFile(v) { return typeof v === "string" && v.toLowerCase().endsWith(".femto"); }
function isAudioFile(v) { return typeof v === "string" && /\.(wav|mp3|raw|bin)$/i.test(v); }
function fmtSize(n) {
  if (n < 1024) return n + " B";
  if (n < 1048576) return (n / 1024).toFixed(1) + " KB";
  return (n / 1048576).toFixed(2) + " MB";
}
function setNestedValue(obj, path, value) {
  const next = Array.isArray(obj) ? [...obj] : { ...obj };
  if (path.length === 1) { next[path[0]] = value; return next; }
  next[path[0]] = setNestedValue(next[path[0]], path.slice(1), value);
  return next;
}

/* ---- Icons (inline SVG) ---- */
const CFI = {
  upload:  '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>',
  reboot:  '<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/>',
  file:    '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
  x:       '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
  check:   '<path d="M20 6 9 17l-5-5"/>',
  alert:   '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
  info:    '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
  chip:    '<path d="M12 2H2v10l9.29 9.29c.94.94 2.48.94 3.42 0l6.58-6.58c.94-.94.94-2.48 0-3.42L12 2Z"/><path d="M7 7h.01"/>',
  device:  '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="3" x2="12" y2="15"/>',
  spinner: '<path d="M21 12a9 9 0 1 1-6.219-8.56"/>',
};
function CFIcon({ name, style }) {
  return (
    <svg style={{ width: 16, height: 16, flexShrink: 0, ...style }} viewBox="0 0 24 24"
      fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"
      dangerouslySetInnerHTML={{ __html: CFI[name] || "" }} />
  );
}

/* ---- Field badge for filename hints ---- */
function FileBadge({ value }) {
  if (isFemtoFile(value)) return <span className="cf-file-badge cf-badge-femto">model</span>;
  if (isAudioFile(value))  return <span className="cf-file-badge cf-badge-audio">audio</span>;
  return null;
}

/* ---- Individual field renderer ---- */
function ConfigField({ path, value, onChange, onRenameKey }) {
  const key   = path[path.length - 1];
  const label = humanLabel(String(key));
  const type  = typeof value;

  if (type === "boolean") {
    return (
      <div className="cf-field">
        <span className="cf-label">{label}</span>
        <div className="cf-radio-group">
          <label className="cf-radio">
            <input type="radio" name={path.join(".")} checked={value === true}
              onChange={() => onChange(path, true)} />
            <span className="cf-radio-dot"></span>
            <span>True</span>
          </label>
          <label className="cf-radio">
            <input type="radio" name={path.join(".")} checked={value === false}
              onChange={() => onChange(path, false)} />
            <span className="cf-radio-dot"></span>
            <span>False</span>
          </label>
        </div>
      </div>
    );
  }

  if (type === "number") {
    const isInt = Number.isInteger(value);
    return (
      <div className="cf-field">
        <span className="cf-label">{label} <span className="cf-type-hint">{isInt ? "integer" : "float"}</span></span>
        <input className="cf-input" type="number" step={isInt ? 1 : "any"} value={value}
          onChange={e => {
            const n = isInt ? parseInt(e.target.value, 10) : parseFloat(e.target.value);
            if (!isNaN(n)) onChange(path, n);
          }} />
      </div>
    );
  }

  if (type === "string") {
    return (
      <div className="cf-field">
        <span className="cf-label">
          {label}
          <FileBadge value={value} />
        </span>
        <input className="cf-input" type="text" value={value}
          onChange={e => onChange(path, e.target.value)}
          placeholder={isFemtoFile(value) ? "e.g. model.femto" : ""} />
      </div>
    );
  }

  if (Array.isArray(value)) {
    return (
      <div className="cf-field cf-field--wide">
        <span className="cf-label">{label} <span className="cf-type-hint">array</span></span>
        <textarea className="cf-input cf-textarea" rows={3}
          defaultValue={JSON.stringify(value, null, 2)}
          onBlur={e => { try { onChange(path, JSON.parse(e.target.value)); } catch (_) {} }} />
      </div>
    );
  }

  if (type === "object" && value !== null) {
    return (
      <div className="cf-section">
        {onRenameKey ? (
          <div className="cf-section-title-edit">
            <span className="cf-label" style={{ minWidth: 90 }}>Model filename</span>
            <input
              className="cf-input cf-key-input"
              type="text"
              defaultValue={path[path.length - 1]}
              onBlur={e => { if (e.target.value.trim() && e.target.value !== path[path.length - 1]) onRenameKey(e.target.value.trim()); }}
              onKeyDown={e => { if (e.key === "Enter") e.target.blur(); }}
            />
            <span className="cf-file-badge cf-badge-femto">model</span>
          </div>
        ) : (
          <div className="cf-section-title">{label}</div>
        )}
        <div className="cf-fields-grid">
          {Object.entries(value).map(([k, v]) => (
            <ConfigField key={k} path={[...path, k]} value={v} onChange={onChange} />
          ))}
        </div>
      </div>
    );
  }

  // null or unknown — text input
  return (
    <div className="cf-field">
      <span className="cf-label">{label}</span>
      <input className="cf-input" type="text" value={value == null ? "" : String(value)}
        onChange={e => onChange(path, e.target.value)} />
    </div>
  );
}

/* ---- Drop zone to load the JSON file ---- */
function JsonDropZone({ onFile, loaded, filename, onClear, onReload }) {
  const [dragging, setDragging] = useState(false);
  const inputRef = useRef(null);

  const readFile = (file) => {
    if (!file) return;
    if (!file.name.endsWith(".json")) {
      alert("Please select a JSON file.");
      return;
    }
    onFile(file);
  };

  if (loaded) {
    return (
      <div className="cf-loaded-file">
        <CFIcon name="file" style={{ color: "var(--color-indigo)", width: 20, height: 20 }} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="cf-loaded-name">{filename}</div>
          <div className="cf-loaded-hint">Editing local copy — click <b>Write to SD Card</b> to apply</div>
        </div>
        {onReload && (
          <button className="fm-btn fm-btn-ghost fm-btn-sm" onClick={onReload} title="Re-read from device">
            <CFIcon name="device" />Reload from device
          </button>
        )}
        <button className="cf-icon-btn" onClick={onClear} title="Load a different file"><CFIcon name="x" /></button>
      </div>
    );
  }

  return (
    <div
      className={"cf-dropzone" + (dragging ? " cf-dropzone--over" : "")}
      onDragOver={e => { e.preventDefault(); setDragging(true); }}
      onDragLeave={() => setDragging(false)}
      onDrop={e => { e.preventDefault(); setDragging(false); readFile(e.dataTransfer.files[0]); }}
      onClick={() => inputRef.current?.click()}
    >
      <input ref={inputRef} type="file" accept=".json" style={{ display: "none" }}
        onChange={e => { readFile(e.target.files[0]); e.target.value = ""; }} />
      <CFIcon name="file" style={{ width: 28, height: 28, color: "var(--color-indigo)" }} />
      <div className="cf-dropzone-title">Drop <code>system_config.json</code> here, or click to browse</div>
      <div className="cf-dropzone-hint">Load a local copy of the config to edit</div>
    </div>
  );
}

/* ---- Inline upload progress ---- */
function InlineProgress({ progress, onCancel }) {
  const pct = progress.totalBytes > 0 ? Math.round((progress.bytesTransferred / progress.totalBytes) * 100) : 0;
  return (
    <div className="cf-progress-wrap">
      <div className="cf-progress-header">
        <span>Uploading… {fmtSize(progress.bytesTransferred)} / {fmtSize(progress.totalBytes)}</span>
        <span>Chunk {progress.chunk} / {progress.totalChunks} · {pct}%</span>
        {onCancel && <button className="cf-icon-btn" onClick={onCancel}><CFIcon name="x" /></button>}
      </div>
      <div className="cf-progress-track">
        <div className="cf-progress-fill" style={{ width: pct + "%" }}></div>
      </div>
    </div>
  );
}

/* ---- Main component ---- */
function ConfigEditor({ protocolCtrl, connState, addLog }) {
  const [edits, setEdits]               = useState(null);
  const [origJson, setOrigJson]         = useState(null);
  const [filename, setFilename]         = useState("system_config.json");
  const [isDirty, setIsDirty]           = useState(false);
  const [parseError, setParseError]     = useState(null);
  const [uploadState, setUploadState]   = useState("idle");
  const [uploadProgress, setUploadProgress] = useState({ chunk:0, totalChunks:0, bytesTransferred:0, totalBytes:0, rateBps:0 });
  const [uploadError, setUploadError]   = useState(null);
  const [rebootState, setRebootState]   = useState("idle");
  const [deviceLoading, setDeviceLoading] = useState(false);
  const [deviceError, setDeviceError]   = useState(null);
  const abortRef = useRef(null);

  const isOpen = connState === "open";
  const uploading = uploadState === "uploading";
  const hasConfig = edits !== null;

  /* Load JSON from a local File object */
  const applyJson = useCallback((text, name) => {
    const parsed = JSON.parse(text);
    if (typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Config must be a JSON object.");
    setOrigJson(parsed);
    setEdits(JSON.parse(text));
    setFilename(name);
    setIsDirty(false);
    setUploadState("idle");
    setDeviceError(null);
  }, []);

  /* Load config directly from the device via CMD:READ */
  const loadFromDevice = useCallback(async () => {
    if (!isOpen || deviceLoading) return;
    setDeviceLoading(true);
    setDeviceError(null);
    setParseError(null);
    try {
      addLog("Reading system_config.json from device…");
      const bytes = await protocolCtrl.read("system_config.json");
      const text = new TextDecoder().decode(bytes);
      applyJson(text, "system_config.json");
      addLog("Config loaded from device.");
    } catch (e) {
      setDeviceError(e.message || "Read failed");
      addLog("Config read error: " + (e.message || e));
    } finally {
      setDeviceLoading(false);
    }
  }, [isOpen, deviceLoading, protocolCtrl, applyJson, addLog]);

  /* Auto-load when device connects and no config is loaded yet */
  useEffect(() => {
    if (isOpen && !hasConfig) loadFromDevice();
  }, [isOpen]); // eslint-disable-line

  const handleFile = useCallback(async (file) => {
    setParseError(null);
    setDeviceError(null);
    try {
      const text = await file.text();
      applyJson(text, file.name);
    } catch (e) {
      setParseError(e.message);
    }
  }, [applyJson]);

  const handleFieldChange = useCallback((path, value) => {
    setEdits(prev => setNestedValue(prev, path, value));
    setIsDirty(true);
  }, []);

  const handleKeyRename = useCallback((oldKey, newKey) => {
    if (!newKey || newKey === oldKey) return;
    setEdits(prev => {
      const entries = Object.entries(prev);
      const idx = entries.findIndex(([k]) => k === oldKey);
      if (idx < 0) return prev;
      entries[idx] = [newKey, entries[idx][1]];
      return Object.fromEntries(entries);
    });
    setIsDirty(true);
  }, []);

  const handleClear = () => {
    setEdits(null); setOrigJson(null); setIsDirty(false);
    setUploadState("idle"); setUploadError(null); setDeviceError(null);
  };

  const handleWrite = async () => {
    if (!edits || uploading) return;
    const jsonStr = JSON.stringify(edits, null, 2);
    const blob = new Blob([jsonStr], { type: "application/json" });
    const file = new File([blob], filename, { type: "application/json" });

    const ac = new AbortController();
    abortRef.current = ac;
    setUploadState("uploading");
    setUploadError(null);
    setUploadProgress({ chunk:0, totalChunks: Math.ceil(file.size/512), bytesTransferred:0, totalBytes:file.size, rateBps:0 });

    try {
      await protocolCtrl.upload(file, {
        signal: ac.signal,
        onProgress: p => setUploadProgress(p),
        onLog: msg => addLog(msg),
      });
      setUploadState("done");
      setIsDirty(false);
      addLog("Config written: " + filename);
    } catch (e) {
      if (e.message === "Upload cancelled.") { setUploadState("idle"); addLog("Upload cancelled."); }
      else { setUploadState("error"); setUploadError(e.message); addLog("Config upload error: " + e.message); }
    }
    abortRef.current = null;
  };

  const handleCancel = () => { abortRef.current?.abort(); };

  const handleReboot = async () => {
    setRebootState("rebooting");
    addLog("Rebooting device…");
    await protocolCtrl.reboot();
    addLog("Reboot command sent.");
    setRebootState("done");
    setTimeout(() => setRebootState("idle"), 3000);
  };

  /* ---- Render ---- */

  if (deviceLoading) {
    return (
      <div className="cf-wrap">
        <div className="cf-loading">
          <CFIcon name="spinner" style={{ width: 28, height: 28, color: "var(--color-indigo)", animation: "spin 1s linear infinite" }} />
          <div className="cf-loading-text">Reading system_config.json from device…</div>
        </div>
      </div>
    );
  }

  return (
    <div className="cf-wrap">

      {deviceError && (
        <div className="cf-banner cf-banner-error">
          <CFIcon name="alert" style={{ color:"var(--color-spark)" }} />
          <span>Could not read from device: {deviceError}</span>
          <button className="cf-icon-btn" onClick={() => setDeviceError(null)} style={{ marginLeft:"auto" }}><CFIcon name="x" /></button>
        </div>
      )}

      {/* File load row */}
      <JsonDropZone
        onFile={handleFile}
        loaded={hasConfig}
        filename={filename}
        onClear={handleClear}
        onReload={isOpen ? loadFromDevice : null}
      />

      {parseError && (
        <div className="cf-banner cf-banner-error">
          <CFIcon name="alert" style={{ color:"var(--color-spark)" }} />
          {parseError}
        </div>
      )}

      {!hasConfig && (
        <div className="cf-hint-card">
          <CFIcon name="info" style={{ color:"var(--color-indigo)", opacity:0.6 }} />
          <span>
            Load a local copy of <code>system_config.json</code> from your computer. Edit the fields below, then click <b>Write to SD Card</b> to upload the modified config. A reboot applies the changes.
            {!isOpen && <span style={{color:"var(--color-spark)"}}> Connect a device first.</span>}
          </span>
        </div>
      )}

      {/* Upload progress */}
      {uploading && <InlineProgress progress={uploadProgress} onCancel={handleCancel} />}

      {/* Success / error banners */}
      {uploadState === "done" && (
        <div className="cf-banner cf-banner-success">
          <CFIcon name="check" style={{ color:"#1aa86a" }} />
          <span><b>{filename}</b> written to SD card. Reboot to apply.</span>
          <button className="cf-icon-btn" onClick={() => setUploadState("idle")} style={{ marginLeft:"auto" }}><CFIcon name="x" /></button>
        </div>
      )}
      {uploadState === "error" && (
        <div className="cf-banner cf-banner-error">
          <CFIcon name="alert" style={{ color:"var(--color-spark)" }} />
          {uploadError}
          <button className="cf-icon-btn" onClick={() => setUploadState("idle")} style={{ marginLeft:"auto" }}><CFIcon name="x" /></button>
        </div>
      )}

      {/* ---- Fields ---- */}
      {hasConfig && !uploading && (
        <div className="cf-form">
          <div className="cf-form-header">
            <span className="cf-form-title">Parameters</span>
            {isDirty && <span className="cf-dirty-badge">Modified</span>}
          </div>
          <div className="cf-fields-grid">
            {Object.entries(edits).map(([k, v]) => (
              <ConfigField key={k} path={[k]} value={v} onChange={handleFieldChange}
                onRenameKey={typeof v === "object" && v !== null && /\.\w+$/.test(k)
                  ? (newKey) => handleKeyRename(k, newKey)
                  : null} />
            ))}
          </div>
        </div>
      )}

      {/* ---- Spacer so footer doesn't cover content ---- */}
      <div style={{ height: 96 }}></div>

      {/* ---- Sticky footer ---- */}
      <div className="cf-footer">
        <div className="cf-footer-inner">
          {hasConfig && (
            <div className="cf-footer-note">
              <CFIcon name="info" style={{ opacity: 0.5 }} />
              Writing will temporarily stop audio playback (CMD:STOP).
            </div>
          )}
          <div className="cf-footer-actions">
            <button
              className="fm-btn fm-btn-ghost"
              onClick={handleReboot}
              disabled={!isOpen || rebootState === "rebooting"}
              title="CMD:REBOOT — applies any new files on the SD card"
            >
              <CFIcon name="reboot" />
              {rebootState === "rebooting" ? "Rebooting…" : rebootState === "done" ? "Rebooted" : "Reboot Device"}
            </button>
            <button
              className="fm-btn fm-btn-primary"
              onClick={handleWrite}
              disabled={!isOpen || !hasConfig || !isDirty || uploading}
            >
              <CFIcon name="upload" />
              Write to SD Card
            </button>
          </div>
        </div>
      </div>

    </div>
  );
}

Object.assign(window, { ConfigEditor });
