// PHYSIO SAMOS — admin console: images. The library page, the picker the editor opens, and the
// in-browser processing every upload goes through.
(() => {
  const { useState, useRef, useCallback } = React;
  const PSA = window.PSA;
  const { Icon, api, toast, confirm, fmtDate, fmtBytes, errorText, Field } = PSA;

  const MAX_INPUT = 30 * 1024 * 1024;
  const toBlob = (canvas, type, quality) => new Promise((resolve) => canvas.toBlob(resolve, type, quality));

  // Resize and re-encode before anything leaves the device. Re-encoding through a canvas is also
  // what removes EXIF and GPS: phone photos of the premises carry coordinates (AGENTS.md, `-strip`).
  // Sizes: at most 1600px wide, plus an 800px copy when wider — the rounding must match mediaInfo()
  // in assets/js/blog-text.js and the check in worker/admin/api-media.js, or the upload is refused.
  // Quality follows the site's own photography (WebP 86, JPEG 88). Safari cannot encode WebP from a
  // canvas and silently hands back PNG, so the result's type is checked and JPEG used instead.
  PSA.processImage = async (file) => {
    if (file.size > MAX_INPUT) throw new Error("Η εικόνα είναι μεγαλύτερη από 30 MB.");
    let bitmap;
    try {
      bitmap = await createImageBitmap(file, { imageOrientation: "from-image" });
    } catch (e) {
      throw new Error(/heic|heif/i.test(`${file.type} ${file.name}`)
        ? "Ο φυλλομετρητής δεν ανοίγει φωτογραφίες HEIC. Αποθηκεύστε την ως JPEG και δοκιμάστε ξανά."
        : "Το αρχείο δεν διαβάζεται ως εικόνα.");
    }
    const width = Math.min(bitmap.width, 1600);
    const height = Math.round((bitmap.height * width) / bitmap.width);
    if (height > 6000) throw new Error("Η εικόνα είναι υπερβολικά ψηλή.");
    const render = (w, h, type) => {
      const canvas = document.createElement("canvas");
      canvas.width = w;
      canvas.height = h;
      const ctx = canvas.getContext("2d");
      ctx.imageSmoothingQuality = "high";
      if (type === "image/jpeg") { ctx.fillStyle = "#ffffff"; ctx.fillRect(0, 0, w, h); }
      ctx.drawImage(bitmap, 0, 0, w, h);
      return toBlob(canvas, type, type === "image/webp" ? 0.86 : 0.88);
    };
    let type = "image/webp";
    let large = await render(width, height, type);
    if (!large || large.type !== "image/webp") { type = "image/jpeg"; large = await render(width, height, type); }
    const small = width > 800 ? await render(800, Math.round((height * 800) / width), type) : null;
    if (bitmap.close) bitmap.close();
    return { large, small, width, height, type };
  };

  PSA.uploadImage = async (file, alt = "") => {
    const p = await PSA.processImage(file);
    const ext = p.type === "image/webp" ? "webp" : "jpg";
    const base = file.name.replace(/\.[^.]+$/, "").slice(0, 120) || "image";
    const form = new FormData();
    form.append("large", p.large, `${base}.${ext}`);
    if (p.small) form.append("small", p.small, `${base}-800.${ext}`);
    form.append("filename", file.name.slice(0, 200));
    form.append("alt", alt);
    return (await api("/api/media", { method: "POST", form })).media;
  };

  // One upload at a time, each row showing its own progress or its own error.
  PSA.useUploads = (onDone) => {
    const [queue, setQueue] = useState([]);
    const done = useRef(onDone);
    done.current = onDone;
    const add = useCallback(async (files) => {
      const rows = [...files].map((file) => ({ id: Math.random().toString(36).slice(2), name: file.name, file, status: "working" }));
      setQueue((q) => [...q, ...rows.map(({ file, ...r }) => r)]);
      for (const row of rows) {
        try {
          const media = await PSA.uploadImage(row.file);
          setQueue((q) => q.filter((x) => x.id !== row.id));
          if (done.current) done.current(media);
        } catch (err) {
          setQueue((q) => q.map((x) => (x.id === row.id ? { ...x, status: "error", message: errorText(err) } : x)));
        }
      }
    }, []);
    const dismiss = (id) => setQueue((q) => q.filter((x) => x.id !== id));
    return { queue, add, dismiss };
  };

  PSA.Dropzone = ({ onFiles }) => {
    const [over, setOver] = useState(false);
    const input = useRef(null);
    const accept = (list) => [...list].filter((f) => /^image\//.test(f.type) || /\.(heic|heif)$/i.test(f.name));
    return (
      <div className={`dropzone${over ? " is-over" : ""}`}
           onDragOver={(e) => { e.preventDefault(); setOver(true); }}
           onDragLeave={() => setOver(false)}
           onDrop={(e) => { e.preventDefault(); setOver(false); const files = accept(e.dataTransfer.files); if (files.length) onFiles(files); }}>
        <Icon name="upload" size="lg" />
        <p><strong>Σύρετε φωτογραφίες εδώ</strong> ή</p>
        <button type="button" className="btn btn--sm" onClick={() => input.current.click()}>Επιλογή από τον υπολογιστή</button>
        <p className="hint">JPEG, PNG ή WebP. Μικραίνουν αυτόματα και αφαιρούνται τα στοιχεία τοποθεσίας πριν ανέβουν.</p>
        <input ref={input} type="file" hidden multiple accept="image/jpeg,image/png,image/webp,image/heic,image/heif"
               onChange={(e) => { const files = accept(e.target.files); e.target.value = ""; if (files.length) onFiles(files); }} />
      </div>
    );
  };

  PSA.UploadQueue = ({ queue, dismiss }) => (queue.length ? (
    <ul className="list-plain" style={{ display: "grid", gap: 6, marginTop: 12 }}>
      {queue.map((u) => (
        <li key={u.id} className="upload-row">
          <Icon name={u.status === "working" ? "loader" : "warning"} size="sm" className={u.status === "working" ? "spin" : ""} />
          <span style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{u.name}</span>
          {u.status === "working"
            ? <span className="muted">Επεξεργασία και αποστολή…</span>
            : <><span style={{ color: "var(--danger)", fontWeight: 600 }}>{u.message}</span>
                <button type="button" className="btn btn--quiet btn--icon btn--sm" aria-label="Απόρριψη" onClick={() => dismiss(u.id)}><Icon name="close" size="sm" /></button></>}
        </li>
      ))}
    </ul>
  ) : null);

  const usedLabel = (m) => (m.usedBy && m.usedBy.length ? ` · σε ${PSA.plural(m.usedBy.length, "άρθρο", "άρθρα")}` : "");

  const MediaTile = ({ m, selected, onClick }) => (
    <button type="button" className="media-tile" aria-pressed={selected === undefined ? undefined : String(!!selected)} onClick={onClick}>
      <span className="media-tile__img"><img src={m.smallUrl || m.url} alt="" loading="lazy" draggable="false" /></span>
      <span className="media-tile__name">{m.filename}</span>
      <span className="media-tile__meta">{m.width}×{m.height} · {fmtBytes(m.size)}{usedLabel(m)}</span>
    </button>
  );

  const MediaGridState = ({ lib, items, emptyTitle, emptyText, children }) => {
    if (lib.loading && !lib.data) {
      return <div className="media-grid" aria-busy="true">{Array.from({ length: 6 }, (_, i) => <div key={i} className="skeleton" style={{ aspectRatio: "4 / 3" }} />)}</div>;
    }
    if (lib.error && !lib.data) return <PSA.ErrorNotice error={lib.error} onRetry={lib.reload} />;
    if (!items.length) return <div className="empty"><Icon name="media" size="lg" /><h3>{emptyTitle}</h3><p>{emptyText}</p></div>;
    return children;
  };

  const MediaDetail = ({ m, onClose, onChanged, onDeleted }) => {
    const [alt, setAlt] = useState(m.alt || "");
    const [busy, setBusy] = useState(false);
    const used = m.usedBy || [];
    const saveAlt = async () => {
      setBusy(true);
      try { onChanged((await api(`/api/media/${m.id}`, { method: "PATCH", body: { alt } })).media); toast("Η περιγραφή αποθηκεύτηκε."); }
      catch (err) { toast(errorText(err), { kind: "error" }); }
      finally { setBusy(false); }
    };
    const remove = async () => {
      const ok = await confirm({ title: "Οριστική διαγραφή εικόνας;", body: "Η εικόνα θα σβηστεί από τη βιβλιοθήκη και από τον ιστότοπο. Δεν αναιρείται.", confirmLabel: "Διαγραφή", danger: true });
      if (!ok) return;
      setBusy(true);
      try { await api(`/api/media/${m.id}`, { method: "DELETE" }); onDeleted(m.id); onClose(); toast("Η εικόνα διαγράφηκε."); }
      catch (err) { toast(errorText(err), { kind: "error" }); setBusy(false); }
    };
    // Images go into a post through the editor's "Εικόνα" button; nothing here hands out Markdown.
    return (
      <PSA.Dialog open wide title={m.filename} onClose={onClose} footer={<>
        <button type="button" className="btn btn--danger" disabled={used.length > 0 || busy} onClick={remove}
                title={used.length ? "Αφαιρέστε την πρώτα από τα άρθρα που τη χρησιμοποιούν" : undefined}>
          <Icon name="trash" size="sm" />Διαγραφή
        </button>
        <span style={{ flex: 1 }} />
        <button type="button" className="btn" onClick={onClose}>Κλείσιμο</button>
      </>}>
        <div className="media-detail">
          <img src={m.url} alt={alt} width={m.width} height={m.height} draggable="false" />
          <div style={{ display: "grid", gap: 16, alignContent: "start" }}>
            <div className="publish-state">
              <dl>
                <dt>Διαστάσεις</dt><dd className="mono">{m.width} × {m.height}{m.smallUrl ? " και 800 πλάτος" : ""}</dd>
                <dt>Μέγεθος</dt><dd className="mono">{fmtBytes(m.size)} · {m.contentType.replace("image/", "").toUpperCase()}</dd>
                <dt>Ανέβηκε</dt><dd>{fmtDate(m.createdAt)}</dd>
              </dl>
            </div>
            <Field label="Περιγραφή εικόνας" htmlFor="media-alt" count={alt.length} max={250}
                   hint="Τι δείχνει η εικόνα, με λίγα λόγια. Τη διαβάζουν όσοι δεν βλέπουν την οθόνη και προτείνεται κάθε φορά που μπαίνει σε άρθρο.">
              <textarea id="media-alt" className="textarea" rows={3} value={alt} onChange={(e) => setAlt(e.target.value)} />
            </Field>
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              <button type="button" className="btn btn--sm" disabled={busy || alt === (m.alt || "")} onClick={saveAlt}>Αποθήκευση περιγραφής</button>
            </div>
            {used.length > 0 && (
              <div className="notice notice--info">
                <Icon name="info" size="sm" />
                <div>
                  Χρησιμοποιείται σε:
                  <ul>{used.map((u) => (
                    <li key={u.id}>
                      <PSA.Link to={`/posts/${u.id}`} onClick={onClose}>{u.title || "Άρθρο χωρίς τίτλο"}</PSA.Link>
                      {u.asCover ? " (εξώφυλλο)" : ""}{u.trashed ? " · στον κάδο" : ""}
                    </li>
                  ))}</ul>
                  Για να διαγραφεί, αφαιρέστε την πρώτα από εκεί.
                </div>
              </div>
            )}
          </div>
        </div>
      </PSA.Dialog>
    );
  };

  PSA.MediaPage = () => {
    const lib = PSA.useAsync(() => api("/api/media"), []);
    const [q, setQ] = useState("");
    const [open, setOpen] = useState(null);
    const uploads = PSA.useUploads((media) => {
      lib.setData((d) => ({ media: [media, ...((d && d.media) || [])] }));
      toast(`Ανέβηκε: ${media.filename}`);
    });
    const all = (lib.data && lib.data.media) || [];
    const needle = q.trim().toLowerCase();
    const items = all.filter((m) => !needle || `${m.filename} ${m.alt}`.toLowerCase().includes(needle));
    return (
      <div className="page">
        <div className="page-head">
          <div>
            <h1>Εικόνες</h1>
            <p>Οι φωτογραφίες των άρθρων. Αποθηκεύονται στον ιστότοπο, ποτέ σε άλλη υπηρεσία.</p>
          </div>
        </div>
        <PSA.Dropzone onFiles={uploads.add} />
        <PSA.UploadQueue queue={uploads.queue} dismiss={uploads.dismiss} />
        <div className="toolbar-row" style={{ marginTop: 28 }}>
          <div className="search">
            <Icon name="search" size="sm" />
            <input className="input" type="search" placeholder="Αναζήτηση με όνομα ή περιγραφή" aria-label="Αναζήτηση εικόνας" value={q} onChange={(e) => setQ(e.target.value)} />
          </div>
          <span className="mono muted">{PSA.plural(items.length, "εικόνα", "εικόνες")}</span>
        </div>
        <MediaGridState lib={lib} items={items}
                        emptyTitle={needle ? "Καμία εικόνα δεν ταιριάζει" : "Η βιβλιοθήκη είναι άδεια"}
                        emptyText={needle ? "Δοκιμάστε άλλη λέξη." : "Ανεβάστε φωτογραφίες εδώ ή απευθείας μέσα από ένα άρθρο."}>
          <div className="media-grid">{items.map((m) => <MediaTile key={m.id} m={m} onClick={() => setOpen(m)} />)}</div>
        </MediaGridState>
        {open && (
          <MediaDetail m={open} onClose={() => setOpen(null)}
                       onChanged={(m) => { setOpen(m); lib.setData((d) => ({ media: d.media.map((x) => (x.id === m.id ? m : x)) })); }}
                       onDeleted={(id) => lib.setData((d) => ({ media: d.media.filter((x) => x.id !== id) }))} />
        )}
      </div>
    );
  };

  // The editor's picker. `mode` "cover" asks for the description only; "insert" also offers a caption.
  // A description is required either way: every image a post shows needs one to publish.
  // `initialMedia` preselects an image — the editor passes one it has just uploaded from a paste or a
  // drop, so the picker opens straight on its description.
  PSA.MediaPicker = ({ mode = "insert", initialAlt = "", initialMedia = null, onClose, onPick }) => {
    const lib = PSA.useAsync(() => api("/api/media"), []);
    const [selected, setSelected] = useState(initialMedia);
    const [alt, setAlt] = useState(initialAlt || (initialMedia && initialMedia.alt) || "");
    const [caption, setCaption] = useState("");
    const select = (m) => { setSelected(m); if (!alt.trim()) setAlt(m.alt || ""); };
    const uploads = PSA.useUploads((media) => {
      lib.setData((d) => ({ media: [media, ...((d && d.media) || [])] }));
      select(media);
    });
    const items = (lib.data && lib.data.media) || [];
    return (
      <PSA.Dialog open wide title={mode === "cover" ? "Εικόνα εξωφύλλου" : "Εισαγωγή εικόνας"} onClose={onClose} footer={<>
        {selected && !alt.trim() && <span className="hint" style={{ marginRight: "auto" }}>Γράψτε μια περιγραφή για να συνεχίσετε.</span>}
        <button type="button" className="btn" onClick={onClose}>Άκυρο</button>
        <button type="button" className="btn btn--primary" disabled={!selected || !alt.trim()}
                onClick={() => onPick({ media: selected, alt: alt.trim(), caption: caption.trim() })}>
          {mode === "cover" ? "Ορισμός ως εξώφυλλο" : "Εισαγωγή στο κείμενο"}
        </button>
      </>}>
        {selected && (
          <div className="media-detail" style={{ paddingBottom: 6, borderBottom: "1px solid var(--hairline)" }}>
            <img src={selected.url} alt="" width={selected.width} height={selected.height} draggable="false" style={{ maxHeight: 260, objectFit: "contain" }} />
            <div style={{ display: "grid", gap: 14, alignContent: "start" }}>
              <Field label="Περιγραφή εικόνας" htmlFor="pick-alt" count={alt.length} max={250}
                     hint="Τι δείχνει η εικόνα. Απαραίτητη για να δημοσιευτεί το άρθρο.">
                <textarea id="pick-alt" className="textarea" rows={3} value={alt} onChange={(e) => setAlt(e.target.value)} autoFocus />
              </Field>
              {mode === "insert" && (
                <Field label="Λεζάντα" htmlFor="pick-caption" optional count={caption.length} max={200} hint="Εμφανίζεται κάτω από την εικόνα.">
                  <input id="pick-caption" className="input" value={caption} onChange={(e) => setCaption(e.target.value.replace(/"/g, "”"))} />
                </Field>
              )}
            </div>
          </div>
        )}
        <PSA.Dropzone onFiles={uploads.add} />
        <PSA.UploadQueue queue={uploads.queue} dismiss={uploads.dismiss} />
        <MediaGridState lib={lib} items={items} emptyTitle="Η βιβλιοθήκη είναι άδεια" emptyText="Ανεβάστε την πρώτη φωτογραφία παραπάνω.">
          <div className="media-grid" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(150px, 1fr))" }}>
            {items.map((m) => <MediaTile key={m.id} m={m} selected={selected && selected.id === m.id} onClick={() => select(m)} />)}
          </div>
        </MediaGridState>
      </PSA.Dialog>
    );
  };
})();
