/* global React, Icon, IconBtn, useReceiveFrom, SendToButton, copyText, useCopy, db */
const { useState, useEffect, useMemo, useCallback, useRef } = React;

// ============================================================
// REGEX TESTER
// ============================================================
function RegexTool() {
  const [pattern, setPattern] = useState('(\\w+)@(\\w+\\.\\w+)');
  const [flags, setFlags] = useState('g');
  const [text, setText] = useState('Email me at jane@example.com or contact admin@test.org for help.');
  const [replace, setReplace] = useState('');
  const [showReplace, setShowReplace] = useState(false);

  const [regex, error] = useMemo(() => {
    if (!pattern) return [null, null];
    try { return [new RegExp(pattern, flags), null]; }
    catch (e) { return [null, e.message]; }
  }, [pattern, flags]);

  const matches = useMemo(() => {
    if (!regex || !text) return [];
    if (regex.global) return [...text.matchAll(regex)];
    const m = text.match(regex);
    return m ? [m] : [];
  }, [regex, text]);

  const replaced = useMemo(() => {
    if (!regex || !text || !showReplace) return '';
    try { return text.replace(regex, replace); } catch { return ''; }
  }, [regex, text, replace, showReplace]);

  // Highlight matches in text
  const highlighted = useMemo(() => {
    if (!regex || !text) return [{ text, match: false }];
    const out = [];
    let last = 0;
    for (const m of matches) {
      const i = m.index;
      if (i > last) out.push({ text: text.slice(last, i), match: false });
      out.push({ text: m[0], match: true });
      last = i + m[0].length;
      if (!regex.global) break;
    }
    if (last < text.length) out.push({ text: text.slice(last), match: false });
    return out;
  }, [regex, text, matches]);

  const FLAGS = [
    { f: 'g', label: 'Global' },
    { f: 'i', label: 'Case-insensitive' },
    { f: 'm', label: 'Multiline' },
    { f: 's', label: 'Dot all' },
    { f: 'u', label: 'Unicode' },
  ];

  const PRESETS = [
    { label: 'Email',      pattern: '[\\w.+-]+@[\\w-]+\\.[\\w.-]+' },
    { label: 'URL',        pattern: 'https?:\\/\\/[\\w.-]+(?:\\/[^\\s]*)?' },
    { label: 'IPv4',       pattern: '\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b' },
    { label: 'UUID',       pattern: '[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}' },
    { label: 'Hex color',  pattern: '#[\\da-f]{3,8}\\b' },
    { label: 'ISO date',   pattern: '\\d{4}-\\d{2}-\\d{2}' },
    { label: 'Phone (US)', pattern: '\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}' },
    { label: 'Whitespace', pattern: '\\s+' },
  ];

  return (
    <div className="dev-tool">
      <div className="dev-pattern-bar">
        <span className="dev-slash">/</span>
        <input
          className="dev-pattern-input"
          value={pattern}
          onChange={e => setPattern(e.target.value)}
          placeholder="pattern"
          spellCheck={false}
        />
        <span className="dev-slash">/</span>
        <input
          className="dev-flags-input"
          value={flags}
          onChange={e => setFlags(e.target.value.replace(/[^gimsuy]/g, ''))}
          placeholder="gimsuy"
          spellCheck={false}
        />
        <div className="dev-flags-help">
          {FLAGS.map(f => (
            <button
              key={f.f}
              className={`lh-chip ${flags.includes(f.f) ? 'is-active' : ''}`}
              title={f.label}
              onClick={() => setFlags(flags.includes(f.f) ? flags.replace(f.f, '') : flags + f.f)}
            >{f.f}</button>
          ))}
        </div>
        <button className={`lh-chip ${showReplace ? 'is-active' : ''}`} onClick={() => setShowReplace(s => !s)}>
          <Icon name="find_replace" />Replace
        </button>
      </div>

      {error && (
        <div className="enc-error" style={{ marginTop: 12 }}>
          <Icon name="error" /><span>{error}</span>
        </div>
      )}

      <div className="dev-presets">
        <span className="dev-presets-label">Presets:</span>
        {PRESETS.map(p => (
          <button key={p.label} className="lh-chip" onClick={() => setPattern(p.pattern)}>{p.label}</button>
        ))}
      </div>

      <div className="dev-io-pair">
        <div className="enc-io-side">
          <div className="enc-io-head">
            <span>Test string</span>
            <span className="enc-io-count">{matches.length} match{matches.length === 1 ? '' : 'es'}</span>
            <div style={{ flex: 1 }} />
            {text && <button className="lh-chip" onClick={() => setText('')}><Icon name="close" />Clear</button>}
          </div>
          <textarea className="enc-input" value={text} onChange={e => setText(e.target.value)} spellCheck={false} />
          <div className="regex-highlighted">
            {highlighted.map((p, i) => (
              p.match
                ? <mark key={i} className="regex-mark">{p.text}</mark>
                : <span key={i}>{p.text}</span>
            ))}
          </div>
        </div>
        <div className="enc-io-side">
          <div className="enc-io-head">
            <span>{showReplace ? 'Replacement result' : 'Match details'}</span>
            <div style={{ flex: 1 }} />
          </div>
          {showReplace ? (
            <>
              <div style={{ padding: '10px 14px', borderBottom: '1px solid var(--color-border)' }}>
                <input
                  className="lh-input"
                  placeholder="Replacement (use $1, $2 for capture groups)"
                  value={replace}
                  onChange={e => setReplace(e.target.value)}
                  style={{ fontFamily: 'ui-monospace, Menlo, monospace' }}
                />
              </div>
              <textarea className="enc-input is-output" value={replaced} readOnly />
            </>
          ) : (
            <div className="regex-matches">
              {matches.length === 0 ? (
                <div className="lh-empty" style={{ padding: 20 }}>
                  <Icon name="search_off" />
                  <div className="title">No matches</div>
                </div>
              ) : matches.map((m, i) => (
                <div key={i} className="regex-match">
                  <div className="regex-match-head">
                    <span className="regex-match-idx">#{i + 1}</span>
                    <code>{m[0]}</code>
                    <span className="regex-match-pos">at {m.index}</span>
                  </div>
                  {m.length > 1 && (
                    <div className="regex-groups">
                      {m.slice(1).map((g, gi) => (
                        <div key={gi} className="regex-group">
                          <span className="regex-group-i">${gi + 1}</span>
                          <code>{g || '(empty)'}</code>
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ============================================================
// CRON BUILDER
// ============================================================
const CRON_FIELDS = [
  { key: 'minute', label: 'Minute', min: 0, max: 59 },
  { key: 'hour',   label: 'Hour',   min: 0, max: 23 },
  { key: 'dom',    label: 'Day',    min: 1, max: 31 },
  { key: 'month',  label: 'Month',  min: 1, max: 12, names: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] },
  { key: 'dow',    label: 'Weekday',min: 0, max: 6,  names: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'] },
];

function describeCron(expr) {
  const parts = expr.trim().split(/\s+/);
  if (parts.length !== 5) return 'Cron expressions have 5 fields: minute hour day month weekday';
  const [m, h, dom, mon, dow] = parts;
  const v = (s, names) => {
    if (s === '*') return null;
    if (s.startsWith('*/')) return `every ${s.slice(2)}`;
    if (names && /^\d+$/.test(s) && names[+s]) return names[+s];
    return s;
  };
  const minute = v(m), hour = v(h), day = v(dom), month = v(mon, CRON_FIELDS[3].names), weekday = v(dow, CRON_FIELDS[4].names);

  const out = [];
  if (m === '*' && h === '*') out.push('Every minute');
  else if (m !== '*' && h !== '*') out.push(`At ${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}`);
  else if (h === '*') out.push(minute?.startsWith('every') ? minute : `At minute ${m}`);
  else out.push(`Every minute at hour ${h}`);

  if (weekday) out.push(`on ${weekday}`);
  if (day) out.push(`on day ${day} of month`);
  if (month) out.push(`in ${month}`);
  return out.join(', ');
}

const CRON_PRESETS = [
  { label: 'Every minute',          expr: '* * * * *' },
  { label: 'Every 5 minutes',       expr: '*/5 * * * *' },
  { label: 'Every hour',            expr: '0 * * * *' },
  { label: 'Every day at midnight', expr: '0 0 * * *' },
  { label: 'Every weekday 9am',     expr: '0 9 * * 1-5' },
  { label: 'Every Sunday 8am',      expr: '0 8 * * 0' },
  { label: 'First of every month',  expr: '0 0 1 * *' },
  { label: 'Every Mon, Wed, Fri',   expr: '0 9 * * 1,3,5' },
];

function CronTool() {
  const [expr, setExpr] = useState('0 9 * * 1-5');
  const parts = expr.trim().split(/\s+/);
  while (parts.length < 5) parts.push('*');
  const setPart = (i, v) => {
    const next = [...parts]; next[i] = v || '*';
    setExpr(next.slice(0, 5).join(' '));
  };

  const desc = useMemo(() => describeCron(expr), [expr]);
  const [copied, copy] = useCopy();

  return (
    <div className="dev-tool">
      <div className="cron-expr-row">
        <input
          className="cron-expr"
          value={expr}
          onChange={e => setExpr(e.target.value)}
          spellCheck={false}
        />
        <button className="lh-btn" onClick={() => copy(expr)}>
          <Icon name={copied ? 'check' : 'content_copy'} />
          {copied ? 'Copied' : 'Copy'}
        </button>
      </div>
      <div className="cron-desc">
        <Icon name="schedule" />
        <span>{desc}</span>
      </div>

      <div className="cron-fields">
        {CRON_FIELDS.map((f, i) => (
          <div key={f.key} className="cron-field">
            <div className="cron-field-label">{f.label}</div>
            <input
              className="cron-field-input"
              value={parts[i]}
              onChange={e => setPart(i, e.target.value)}
              placeholder="*"
              spellCheck={false}
            />
            <div className="cron-field-range">
              {f.min}–{f.max}{f.names ? '' : ''}
            </div>
          </div>
        ))}
      </div>

      <div className="cron-presets">
        <div className="cron-presets-label">Quick presets</div>
        <div className="cron-presets-list">
          {CRON_PRESETS.map(p => (
            <button
              key={p.label}
              className={`cron-preset ${expr === p.expr ? 'is-active' : ''}`}
              onClick={() => setExpr(p.expr)}
            >
              <code>{p.expr}</code>
              <span>{p.label}</span>
            </button>
          ))}
        </div>
      </div>

      <div className="cron-cheat">
        <div className="cron-cheat-title">Syntax cheat sheet</div>
        <table>
          <tbody>
            <tr><td><code>*</code></td><td>Every value</td></tr>
            <tr><td><code>5</code></td><td>Exact value</td></tr>
            <tr><td><code>1-5</code></td><td>Range</td></tr>
            <tr><td><code>1,3,5</code></td><td>List</td></tr>
            <tr><td><code>*/15</code></td><td>Every 15 units</td></tr>
            <tr><td><code>10-20/2</code></td><td>Range with step</td></tr>
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ============================================================
// COLOR PICKER & PALETTE
// ============================================================
function ColorTool() {
  const [color, setColor] = useState('#7f0df2');
  const [palette, setPalette] = useState([]);

  useEffect(() => {
    db.kv.get('colorPalette').then(r => { if (Array.isArray(r?.v)) setPalette(r.v); });
  }, []);
  const savePalette = (next) => {
    setPalette(next);
    db.kv.put({ k: 'colorPalette', v: next });
  };

  const rgb = hexToRgb(color);
  const hsl = rgb ? rgbToHsl(rgb.r, rgb.g, rgb.b) : null;
  const isLight = rgb ? (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) > 140 : false;

  const formats = rgb ? [
    { label: 'HEX',  value: color.toUpperCase() },
    { label: 'RGB',  value: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})` },
    { label: 'RGBA', value: `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1)` },
    { label: 'HSL',  value: `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)` },
    { label: 'CSS',  value: `--color: ${color};` },
  ] : [];

  const [copiedFmt, copyFmt] = useCopy();
  const onCopy = (label, val) => { copyFmt(val); };

  // Contrast checker against white/black
  const contrastWhite = rgb ? contrastRatio(rgb, { r: 255, g: 255, b: 255 }) : 0;
  const contrastBlack = rgb ? contrastRatio(rgb, { r: 0,   g: 0,   b: 0   }) : 0;

  const ratings = (c) => {
    if (c >= 7) return { label: 'AAA', tone: 'success' };
    if (c >= 4.5) return { label: 'AA', tone: 'success' };
    if (c >= 3) return { label: 'AA Large', tone: 'med' };
    return { label: 'Fail', tone: 'high' };
  };

  // Auto-generate harmonies
  const harmonies = useMemo(() => {
    if (!hsl) return [];
    const c = (h) => hslToHex(((h % 360) + 360) % 360, hsl.s, hsl.l);
    return [
      { label: 'Complementary', colors: [color, c(hsl.h + 180)] },
      { label: 'Analogous',     colors: [c(hsl.h - 30), color, c(hsl.h + 30)] },
      { label: 'Triadic',       colors: [color, c(hsl.h + 120), c(hsl.h + 240)] },
      { label: 'Shades',        colors: [0,15,30,45,60].map(d => hslToHex(hsl.h, hsl.s, Math.max(5, hsl.l - d))) },
      { label: 'Tints',         colors: [0,15,30,45,60].map(d => hslToHex(hsl.h, hsl.s, Math.min(95, hsl.l + d))) },
    ];
  }, [color, hsl]);

  return (
    <div className="dev-tool color-tool">
      <div className="color-main">
        <label className="color-preview" style={{ background: color, color: isLight ? '#000' : '#fff' }}>
          <div className="color-preview-hex">{color.toUpperCase()}</div>
          <input
            type="color"
            value={color}
            onChange={e => setColor(e.target.value)}
            className="color-input-hidden"
          />
          <div className="color-preview-hint">
            <Icon name="colorize" />
            Click anywhere to pick a new color
          </div>
          <button
            type="button"
            className="color-preview-btn"
            onClick={(e) => { e.preventDefault(); e.stopPropagation(); savePalette([color, ...palette.filter(c => c !== color)].slice(0, 24)); }}
          >
            <Icon name="bookmark_add" />Save to palette
          </button>
        </label>
        <div className="color-formats">
          {formats.map(f => (
            <div key={f.label} className="color-format-row">
              <div className="color-format-label">{f.label}</div>
              <code className="color-format-val">{f.value}</code>
              <button className="lh-chip" onClick={() => onCopy(f.label, f.value)}>
                <Icon name="content_copy" />Copy
              </button>
            </div>
          ))}
        </div>
      </div>

      <div className="color-section">
        <div className="color-section-head"><Icon name="contrast" />Contrast</div>
        <div className="color-contrast-grid">
          <div className="color-contrast-card" style={{ background: '#fff', color }}>
            <div className="color-sample">The quick brown fox</div>
            <div className="color-contrast-meta">
              <span>vs white</span>
              <strong>{contrastWhite.toFixed(2)}</strong>
              <span className={`color-rating tone-${ratings(contrastWhite).tone}`}>{ratings(contrastWhite).label}</span>
            </div>
          </div>
          <div className="color-contrast-card" style={{ background: '#000', color }}>
            <div className="color-sample">The quick brown fox</div>
            <div className="color-contrast-meta">
              <span>vs black</span>
              <strong>{contrastBlack.toFixed(2)}</strong>
              <span className={`color-rating tone-${ratings(contrastBlack).tone}`}>{ratings(contrastBlack).label}</span>
            </div>
          </div>
        </div>
      </div>

      <div className="color-section">
        <div className="color-section-head"><Icon name="palette" />Harmonies</div>
        {harmonies.map(h => (
          <div key={h.label} className="color-harmony">
            <div className="color-harmony-label">{h.label}</div>
            <div className="color-harmony-row">
              {h.colors.map((c, i) => (
                <button
                  key={i}
                  className="color-swatch"
                  style={{ background: c }}
                  onClick={() => setColor(c)}
                  title={c}
                >
                  <span>{c.toUpperCase()}</span>
                </button>
              ))}
            </div>
          </div>
        ))}
      </div>

      {palette.length > 0 && (
        <div className="color-section">
          <div className="color-section-head">
            <Icon name="collections_bookmark" />Saved palette
            <span style={{ flex: 1 }} />
            <button className="lh-chip" onClick={() => savePalette([])}>
              <Icon name="delete_sweep" />Clear all
            </button>
          </div>
          <div className="color-palette-grid">
            {palette.map(c => (
              <div key={c} className="color-palette-item">
                <button
                  className="color-swatch lg"
                  style={{ background: c }}
                  onClick={() => setColor(c)}
                >
                  <span>{c.toUpperCase()}</span>
                </button>
                <IconBtn name="close" tone="danger" onClick={() => savePalette(palette.filter(x => x !== c))} />
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function hexToRgb(hex) {
  const m = hex.replace('#','').match(/^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
  if (!m) return null;
  return { r: parseInt(m[1],16), g: parseInt(m[2],16), b: parseInt(m[3],16) };
}
function rgbToHsl(r,g,b) {
  r /= 255; g /= 255; b /= 255;
  const mx = Math.max(r,g,b), mn = Math.min(r,g,b);
  let h, s, l = (mx + mn) / 2;
  if (mx === mn) { h = s = 0; }
  else {
    const d = mx - mn;
    s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
    switch (mx) {
      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
      case g: h = (b - r) / d + 2; break;
      case b: h = (r - g) / d + 4; break;
    }
    h /= 6;
  }
  return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
}
function hslToHex(h, s, l) {
  s /= 100; l /= 100;
  const k = (n) => (n + h / 30) % 12;
  const a = s * Math.min(l, 1 - l);
  const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));
  const toHex = (x) => Math.round(x * 255).toString(16).padStart(2, '0');
  return '#' + toHex(f(0)) + toHex(f(8)) + toHex(f(4));
}
function luminance(rgb) {
  const a = [rgb.r, rgb.g, rgb.b].map(v => {
    v /= 255;
    return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
  });
  return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
function contrastRatio(a, b) {
  const la = luminance(a), lb = luminance(b);
  return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
}

// ============================================================
// QUICK CONVERTER
// ============================================================
const CONVERT_TABS = [
  { id: 'json-yaml', label: 'JSON ↔ YAML', icon: 'sync_alt' },
  { id: 'json-csv',  label: 'JSON ↔ CSV',  icon: 'sync_alt' },
  { id: 'timestamp', label: 'Timestamp ↔ Date', icon: 'schedule' },
  { id: 'units',     label: 'Units', icon: 'straighten' },
];

function ConvertTool() {
  const [tab, setTab] = useState('json-yaml');
  return (
    <div className="dev-tool">
      <div className="encode-tabs">
        {CONVERT_TABS.map(t => (
          <button key={t.id} className={`encode-tab ${tab === t.id ? 'is-active' : ''}`} onClick={() => setTab(t.id)}>
            <Icon name={t.icon} /><span>{t.label}</span>
          </button>
        ))}
      </div>
      {tab === 'json-yaml' && <JsonYaml />}
      {tab === 'json-csv'  && <JsonCsv />}
      {tab === 'timestamp' && <TimestampDate />}
      {tab === 'units'     && <UnitConverter />}
    </div>
  );
}

// JSON ↔ YAML (minimal YAML writer/reader for common cases)
function jsonToYaml(v, indent = 0) {
  const pad = ' '.repeat(indent);
  if (v === null) return 'null';
  if (typeof v === 'boolean' || typeof v === 'number') return String(v);
  if (typeof v === 'string') return v.includes('\n') || v.includes(':') ? JSON.stringify(v) : v;
  if (Array.isArray(v)) {
    if (v.length === 0) return '[]';
    return '\n' + v.map(x => pad + '- ' + (typeof x === 'object' && x !== null ? jsonToYaml(x, indent + 2).trimStart() : jsonToYaml(x, indent + 2))).join('\n');
  }
  if (typeof v === 'object') {
    const keys = Object.keys(v);
    if (keys.length === 0) return '{}';
    return keys.map(k => {
      const val = v[k];
      const sub = jsonToYaml(val, indent + 2);
      if (typeof val === 'object' && val !== null && (!Array.isArray(val) || val.length > 0) && (Array.isArray(val) || Object.keys(val).length > 0)) {
        return pad + k + ':' + (sub.startsWith('\n') ? sub : '\n' + ' '.repeat(indent + 2) + sub);
      }
      return pad + k + ': ' + sub;
    }).join('\n');
  }
  return String(v);
}

function yamlToJson(yaml) {
  // Very small YAML parser — supports nested maps, lists, scalars, quoted strings
  const lines = yaml.split('\n').filter(l => !/^\s*#/.test(l) && l.trim());
  let i = 0;
  function parseBlock(indent) {
    const obj = {};
    const arr = [];
    let isArr = null;
    while (i < lines.length) {
      const line = lines[i];
      const curIndent = line.match(/^ */)[0].length;
      if (curIndent < indent) break;
      if (curIndent > indent) break;
      const trimmed = line.trim();
      if (trimmed.startsWith('- ')) {
        if (isArr === null) isArr = true;
        const after = trimmed.slice(2);
        if (after.includes(':') && !/^["']/.test(after)) {
          // inline map start
          const [k, ...rest] = after.split(':');
          const v = rest.join(':').trim();
          i++;
          const sub = v ? parseScalar(v) : parseBlock(indent + 2);
          if (typeof sub === 'object' && sub !== null && !Array.isArray(sub)) {
            arr.push({ [k.trim()]: v ? parseScalar(v) : null, ...(v ? {} : sub) });
          } else {
            arr.push({ [k.trim()]: v ? parseScalar(v) : sub });
          }
        } else {
          arr.push(parseScalar(after));
          i++;
        }
      } else if (trimmed.includes(':')) {
        if (isArr === null) isArr = false;
        const [k, ...rest] = trimmed.split(':');
        const v = rest.join(':').trim();
        i++;
        obj[k.trim()] = v ? parseScalar(v) : parseBlock(curIndent + 2);
      } else { i++; }
    }
    return isArr ? arr : obj;
  }
  function parseScalar(s) {
    if (s === 'null' || s === '~') return null;
    if (s === 'true') return true;
    if (s === 'false') return false;
    if (/^-?\d+$/.test(s)) return parseInt(s, 10);
    if (/^-?\d+\.\d+$/.test(s)) return parseFloat(s);
    if (/^".*"$/.test(s)) return JSON.parse(s);
    if (/^'.*'$/.test(s)) return s.slice(1,-1);
    return s;
  }
  return parseBlock(0);
}

function JsonYaml() {
  const [json, setJson] = useState('{\n  "name": "Lifehub",\n  "version": "1.0",\n  "tools": ["todo", "notes", "json"]\n}');
  const [yaml, setYaml] = useState('');
  const [error, setError] = useState(null);
  const [dir, setDir] = useState('jy');

  const fromJson = () => {
    try { const obj = JSON.parse(json); setYaml(jsonToYaml(obj).replace(/^\n/, '')); setError(null); }
    catch (e) { setError(e.message); }
  };
  const fromYaml = () => {
    try { const obj = yamlToJson(yaml); setJson(JSON.stringify(obj, null, 2)); setError(null); }
    catch (e) { setError(e.message); }
  };

  useEffect(() => { if (dir === 'jy') fromJson(); /* eslint-disable-next-line */ }, [json, dir]);
  useEffect(() => { if (dir === 'yj') fromYaml(); /* eslint-disable-next-line */ }, [yaml, dir]);

  return (
    <div className="enc-panel">
      <div className="enc-mode-pick">
        <button className={`enc-mode-btn ${dir === 'jy' ? 'is-active' : ''}`} onClick={() => setDir('jy')}>JSON → YAML</button>
        <button className={`enc-mode-btn ${dir === 'yj' ? 'is-active' : ''}`} onClick={() => setDir('yj')}>YAML → JSON</button>
      </div>
      {error && <div className="enc-error inline"><Icon name="error" /><span>{error}</span></div>}
      <div className="enc-io-pair">
        <div className="enc-io-side">
          <div className="enc-io-head"><span>JSON</span><div style={{flex:1}} /></div>
          <textarea className="enc-input" value={json} onChange={e => { setJson(e.target.value); setDir('jy'); }} spellCheck={false} />
        </div>
        <div className="enc-io-side">
          <div className="enc-io-head"><span>YAML</span><div style={{flex:1}} /></div>
          <textarea className="enc-input" value={yaml} onChange={e => { setYaml(e.target.value); setDir('yj'); }} spellCheck={false} />
        </div>
      </div>
    </div>
  );
}

// JSON ↔ CSV
function jsonToCsv(rows) {
  if (!Array.isArray(rows) || rows.length === 0) return '';
  const keys = [...new Set(rows.flatMap(r => Object.keys(r || {})))];
  const esc = (v) => {
    if (v == null) return '';
    const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
    return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
  };
  return [keys.join(','), ...rows.map(r => keys.map(k => esc(r?.[k])).join(','))].join('\n');
}
function csvToJson(csv) {
  const lines = csv.split(/\r?\n/).filter(l => l.length);
  if (lines.length === 0) return [];
  const parseLine = (s) => {
    const out = []; let cur = ''; let inQ = false;
    for (let i = 0; i < s.length; i++) {
      const c = s[i];
      if (inQ) {
        if (c === '"' && s[i+1] === '"') { cur += '"'; i++; }
        else if (c === '"') inQ = false;
        else cur += c;
      } else {
        if (c === ',') { out.push(cur); cur = ''; }
        else if (c === '"') inQ = true;
        else cur += c;
      }
    }
    out.push(cur);
    return out;
  };
  const headers = parseLine(lines[0]);
  return lines.slice(1).map(line => {
    const vals = parseLine(line);
    const obj = {};
    headers.forEach((h, i) => { obj[h] = vals[i] ?? ''; });
    return obj;
  });
}

function JsonCsv() {
  const [json, setJson] = useState('[\n  { "name": "Alice", "age": 30, "role": "admin" },\n  { "name": "Bob",   "age": 25, "role": "editor" }\n]');
  const [csv, setCsv] = useState('');
  const [error, setError] = useState(null);
  const [dir, setDir] = useState('jc');
  useEffect(() => {
    if (dir === 'jc') {
      try { setCsv(jsonToCsv(JSON.parse(json))); setError(null); }
      catch (e) { setError(e.message); }
    }
    // eslint-disable-next-line
  }, [json, dir]);
  useEffect(() => {
    if (dir === 'cj') {
      try { setJson(JSON.stringify(csvToJson(csv), null, 2)); setError(null); }
      catch (e) { setError(e.message); }
    }
    // eslint-disable-next-line
  }, [csv, dir]);

  return (
    <div className="enc-panel">
      <div className="enc-mode-pick">
        <button className={`enc-mode-btn ${dir === 'jc' ? 'is-active' : ''}`} onClick={() => setDir('jc')}>JSON → CSV</button>
        <button className={`enc-mode-btn ${dir === 'cj' ? 'is-active' : ''}`} onClick={() => setDir('cj')}>CSV → JSON</button>
      </div>
      {error && <div className="enc-error inline"><Icon name="error" /><span>{error}</span></div>}
      <div className="enc-io-pair">
        <div className="enc-io-side">
          <div className="enc-io-head"><span>JSON (array of objects)</span><div style={{flex:1}} /></div>
          <textarea className="enc-input" value={json} onChange={e => { setJson(e.target.value); setDir('jc'); }} spellCheck={false} />
        </div>
        <div className="enc-io-side">
          <div className="enc-io-head"><span>CSV</span><div style={{flex:1}} /></div>
          <textarea className="enc-input" value={csv} onChange={e => { setCsv(e.target.value); setDir('cj'); }} spellCheck={false} />
        </div>
      </div>
    </div>
  );
}

// Timestamp ↔ Date
function TimestampDate() {
  const [now, setNow] = useState(Date.now());
  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(id);
  }, []);

  const [ts, setTs] = useState('');
  const tsNum = useMemo(() => {
    if (!ts) return null;
    const n = parseInt(ts, 10);
    if (isNaN(n)) return null;
    // Auto-detect seconds vs ms
    return n < 1e12 ? n * 1000 : n;
  }, [ts]);
  const tsDate = tsNum ? new Date(tsNum) : null;

  return (
    <div className="enc-panel">
      <div className="ts-now">
        <div className="ts-now-card">
          <div className="ts-now-label">Now (ms)</div>
          <code>{now}</code>
        </div>
        <div className="ts-now-card">
          <div className="ts-now-label">Now (s)</div>
          <code>{Math.floor(now / 1000)}</code>
        </div>
        <div className="ts-now-card">
          <div className="ts-now-label">Now (ISO)</div>
          <code>{new Date(now).toISOString()}</code>
        </div>
        <div className="ts-now-card">
          <div className="ts-now-label">Now (local)</div>
          <code>{new Date(now).toLocaleString()}</code>
        </div>
      </div>

      <div className="ts-convert">
        <div className="enc-io-head"><span>Convert timestamp to date</span><div style={{flex:1}} /></div>
        <input
          className="lh-input"
          placeholder="1731234567 or 1731234567890"
          value={ts}
          onChange={e => setTs(e.target.value)}
          style={{ fontFamily: 'ui-monospace, Menlo, monospace' }}
        />
        {tsDate && (
          <div className="ts-result">
            <div><strong>Local:</strong> {tsDate.toLocaleString()}</div>
            <div><strong>UTC:</strong> {tsDate.toUTCString()}</div>
            <div><strong>ISO:</strong> {tsDate.toISOString()}</div>
            <div><strong>Relative:</strong> {Math.round((tsNum - now) / 1000)}s from now</div>
          </div>
        )}
      </div>
    </div>
  );
}

// Unit converter — common ones
const UNIT_GROUPS = [
  { label: 'Length', units: [['m',1],['cm',0.01],['mm',0.001],['km',1000],['in',0.0254],['ft',0.3048],['yd',0.9144],['mi',1609.344]] },
  { label: 'Weight', units: [['kg',1],['g',0.001],['mg',0.000001],['lb',0.453592],['oz',0.0283495]] },
  { label: 'Temperature', units: 'temp' },
  { label: 'Bytes', units: [['B',1],['KB',1024],['MB',1024**2],['GB',1024**3],['TB',1024**4]] },
];

function UnitConverter() {
  const [groupIdx, setGroupIdx] = useState(0);
  const group = UNIT_GROUPS[groupIdx];
  const [from, setFrom] = useState(group.units === 'temp' ? 'C' : group.units[0][0]);
  const [val, setVal] = useState('1');

  useEffect(() => {
    setFrom(group.units === 'temp' ? 'C' : group.units[0][0]);
  }, [groupIdx]);

  const convert = (toUnit) => {
    const v = parseFloat(val);
    if (isNaN(v)) return '';
    if (group.units === 'temp') {
      // °C base
      let c;
      if (from === 'C') c = v; else if (from === 'F') c = (v - 32) * 5/9; else c = v - 273.15;
      if (toUnit === 'C') return c.toFixed(4);
      if (toUnit === 'F') return (c * 9/5 + 32).toFixed(4);
      if (toUnit === 'K') return (c + 273.15).toFixed(4);
    } else {
      const fromMul = group.units.find(u => u[0] === from)[1];
      const toMul = group.units.find(u => u[0] === toUnit)[1];
      return (v * fromMul / toMul).toLocaleString(undefined, { maximumFractionDigits: 6 });
    }
  };

  const list = group.units === 'temp' ? [['C'],['F'],['K']] : group.units;

  return (
    <div className="enc-panel">
      <div className="enc-mode-pick">
        {UNIT_GROUPS.map((g, i) => (
          <button key={g.label} className={`enc-mode-btn ${groupIdx === i ? 'is-active' : ''}`} onClick={() => setGroupIdx(i)}>
            {g.label}
          </button>
        ))}
      </div>
      <div className="unit-row">
        <input className="lh-input" value={val} onChange={e => setVal(e.target.value)} type="number" style={{ width: 140 }} />
        <select className="lh-input" value={from} onChange={e => setFrom(e.target.value)} style={{ width: 100 }}>
          {list.map(([u]) => <option key={u} value={u}>{u}</option>)}
        </select>
      </div>
      <div className="unit-results">
        {list.filter(([u]) => u !== from).map(([u]) => (
          <div key={u} className="unit-result-row">
            <span className="unit-result-label">{u}</span>
            <code>{convert(u)}</code>
          </div>
        ))}
      </div>
    </div>
  );
}

Object.assign(window, { RegexTool, CronTool, ColorTool, ConvertTool });
