// App root

const { CATEGORIES: A_CATS, fetchArticles, KIDDEN_FEATURES, supabase } = window.KS_DATA;

let STYLE_REFERENCE_IMAGES = [];

const STYLE_PREFIX = "Create an illustration in a soft 2.5D editorial mobile-app style for a parental control / family safety product. Visual style: Use a warm, polished, softly volumetric illustration style that sits between flat 2D and 3D. The image should have rounded shapes, gentle depth, smooth gradients, and soft lighting, but it should not look like full 3D rendering. The overall look should feel hand-crafted, calm, trustworthy, and modern — suitable for parents, not childish or overly playful. Characters: Use stylized human characters with simplified, rounded proportions. Faces should be minimal and expressive: small oval dark eyes, simple eyebrows, tiny nose, and subtle mouth. Avoid realistic facial detail. Characters should feel warm and relatable, with soft hair shapes, simplified hands, and casual clothing such as hoodies, sweaters, T-shirts, loose pants, and sneakers. Rendering: Use a soft 2.5D look with subtle volume, layered shapes, gentle shading, and smooth gradients. Surfaces should feel matte and softly painted rather than plastic or realistic. Add mild ambient shadows, soft highlights, and delicate depth, while keeping the illustration clean and minimal. Color palette: Use muted pastel colors with warm undertones. Preferred colors include soft blues, lavender, sage green, teal, peach, warm yellow, muted orange, beige, dusty purple, and warm gray. Avoid neon colors, pure black, harsh red, or overly saturated tones. Lighting: Use warm, diffused ambient lighting. Screens, icons, or UI elements may have a soft glow, but the glow should be subtle and atmospheric. Avoid dramatic cinematic lighting, hard shadows, or strong contrast. Composition: Use a clean, centered composition with one clear visual idea. The image should remain readable at small mobile sizes. Important characters or objects should be large, simple, and easy to understand. Supporting elements should add context without clutter. Background: Use soft gradient backgrounds or cozy home interiors. Interior details may include rounded sofas, lamps, shelves, plants, rugs, picture frames, cabinets, clocks, and simple wall decor. Background objects should be softened and slightly lower contrast so the main subject remains dominant. Icons and UI elements: When showing digital safety concepts, use floating rounded icons with soft glow circles, such as a lock, shield, clock, settings gear, location pin, message envelope, or profile card. Phone screens should show simplified UI only — abstract shapes, blank avatars, lines, cards, and buttons. Do not include readable text, logos, brand names, or complex interfaces. Mood: The mood should be gentle, thoughtful, and emotionally clear. Even when showing concern, conflict, screen overuse, privacy, or family disagreement, keep the tone soft and non-alarming. Avoid scary, aggressive, dramatic, or judgmental expressions. Texture: Apply subtle grain or painterly softness over the image. The final result should feel slightly hazy, warm, polished, and app-friendly, without harsh digital sharpness. Negative style instructions: Do not use full 3D rendering, photorealistic humans, plastic toy-like shine, flat vector style, anime, comic book style, hard outlines, sharp shadows, exaggerated cartoon expressions, chibi proportions, detailed text, logos, cluttered backgrounds, or high contrast. Now illustrate the following: ";

function loadStyleReference(onDone) {
  const input = document.createElement('input');
  input.type = 'file';
  input.accept = 'image/*';
  input.multiple = true;
  input.onchange = () => {
    const files = Array.from(input.files);
    if (!files.length) return;
    let loaded = 0;
    files.forEach(file => {
      const reader = new FileReader();
      reader.onload = () => {
        const [header, data] = reader.result.split(',');
        const mimeType = header.match(/data:([^;]+)/)?.[1] || 'image/png';
        STYLE_REFERENCE_IMAGES.push({ data, mimeType });
        loaded++;
        if (loaded === files.length && onDone) onDone(STYLE_REFERENCE_IMAGES.length);
      };
      reader.readAsDataURL(file);
    });
  };
  input.click();
}

function NewArticleModal({ onClose, onCreate }) {
  const [title, setTitle] = useState('');
  const [category, setCategory] = useState(A_CATS[0].id);
  return (
    <Modal onClose={onClose}>
      <h2>New article</h2>
      <p>Add a custom topic outside the spreadsheet. The article will open as a draft.</p>
      <div className="field">
        <label className="field-label">Title</label>
        <input className="field-input" autoFocus value={title} onChange={e => setTitle(e.target.value)} placeholder="e.g. Should I let my 12-year-old on TikTok?" />
      </div>
      <div className="field">
        <label className="field-label">Category</label>
        <select className="field-select" value={category} onChange={e => setCategory(e.target.value)}>
          {A_CATS.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
          <option value="__add">+ Add category…</option>
        </select>
      </div>
      <div className="modal-actions">
        <button className="btn" onClick={onClose}>Cancel</button>
        <button className="btn btn-primary" disabled={!title.trim()} onClick={() => onCreate({ title: title.trim(), category })}>
          Create &amp; generate
        </button>
      </div>
    </Modal>
  );
}


function App() {
  const [articles, setArticles] = useState([]);
  const [loading, setLoading] = useState(true);
  const [styleRefCount, setStyleRefCount] = useState(0);
  const [view, setView] = useState({ name: 'dashboard' });
  const [navView, setNavView] = useState('home');
  const [showNewModal, setShowNewModal] = useState(false);
  const [imagesUsed, setImagesUsed] = useState(38);
  const imagesQuota = 400;
  const [toast, setToast] = useState('');
  const [saveState, setSaveState] = useState('saved');
  const saveTimerRef = useRef(null);

  useEffect(() => {
    fetchArticles()
      .then(data => setArticles(data || []))
      .catch(err => console.error('Failed to load articles:', err))
      .finally(() => setLoading(false));
  }, []);

  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "accent": "#0066FF",
    "heroPanel": true,
    "statStrip": true,
    "rounded": "medium"
  }/*EDITMODE-END*/;
  const [tweaks, setTweak] = window.useTweaks(TWEAK_DEFAULTS);

  // Apply tweaks live as CSS variables
  useEffect(() => {
    const root = document.documentElement;
    root.style.setProperty('--accent', tweaks.accent);
    // derive a soft tint by lowering opacity via a generated rgba
    const hex = tweaks.accent.replace('#','');
    const r = parseInt(hex.slice(0,2),16);
    const g = parseInt(hex.slice(2,4),16);
    const b = parseInt(hex.slice(4,6),16);
    root.style.setProperty('--accent-soft', `rgba(${r},${g},${b},0.12)`);
    root.style.setProperty('--grad-cta', `linear-gradient(180deg, ${tweaks.accent} 0%, rgb(${Math.max(0,r-20)},${Math.max(0,g-20)},${Math.max(0,b-20)}) 100%)`);
    root.style.setProperty('--accent-rgb', `${r}, ${g}, ${b}`);
    const radius = tweaks.rounded === 'sharp' ? '6px' : tweaks.rounded === 'soft' ? '18px' : '12px';
    root.style.setProperty('--radius', radius);
    document.body.classList.toggle('no-hero', !tweaks.heroPanel);
    document.body.classList.toggle('no-stat-strip', !tweaks.statStrip);
  }, [tweaks.accent, tweaks.rounded, tweaks.heroPanel, tweaks.statStrip]);

  const showToast = (msg) => {
    setToast(msg);
    setTimeout(() => setToast(''), 1800);
  };

  const handleCopyPrompt = (text) => {
    if (text) navigator.clipboard?.writeText(text);
    showToast('Prompt copied');
  };

  const updateArticle = (id, patch) => {
    setArticles(prev => prev.map(a => a.id === id ? { ...a, ...patch, updated_at: 'just now' } : a));
    setSaveState('saving');
    clearTimeout(saveTimerRef.current);
    saveTimerRef.current = setTimeout(() => setSaveState('saved'), 600);
  };

  const updateStep = (id, idx, patch) => {
    setArticles(prev => prev.map(a => {
      if (a.id !== id) return a;
      const steps = a.steps.map((s, i) => i === idx ? { ...s, ...patch } : s);
      return { ...a, steps, updated_at: 'just now' };
    }));
    setSaveState('saving');
    clearTimeout(saveTimerRef.current);
    saveTimerRef.current = setTimeout(() => setSaveState('saved'), 600);
  };

  const handleGenerateText = async (id) => {
    console.log('Generate triggered', id);
    const a = articles.find(x => x.id === id);
    if (!a) { console.log('Article not found in state:', id, articles.map(x => x.id)); return; }
    showToast('Generating…');
    try {
      console.log('[Claude] sending fetch…');
      const res = await fetch('https://api.anthropic.com/v1/messages', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': CONFIG.CLAUDE_API_KEY,
          'anthropic-version': '2023-06-01',
          'anthropic-dangerous-direct-browser-access': 'true',
        },
        body: JSON.stringify({
          model: 'claude-sonnet-4-5',
          max_tokens: 2048,
          system: `You are a content writer for Kidden, a parental control app. Write practical, warm articles for parents about managing children's digital lives. Return ONLY valid JSON — no markdown, no explanation — in exactly this shape:
{
  "thumbnail_prompt": "A square illustration with a soft gradient background. [topic]. Simplified figures, round heads, minimal facial detail. Calm, warm mood.",
  "steps": [
    {
      "title": "Step title (3–6 words)",
      "body": "2–4 sentences. Direct, practical, warm. No jargon. Written for a tired parent.",
      "image_prompt": "A landscape illustration in soft [color] tones. [title]. Simplified human figures with round heads, minimal facial detail. Calm, warm mood. No text, no UI elements."
    }
  ]
}
Rules: exactly 6 steps. The final step must naturally mention one of these Kidden features: ${KIDDEN_FEATURES.join(', ')}. Use a different soft color per step from: sage, coral, blue, lavender, peach, teal.`,
          messages: [
            { role: 'user', content: `Write a 6-step article titled: "${a.title}". Category: ${a.category}.` },
          ],
        }),
      });
      console.log('[Claude] fetch complete, status:', res.status);
      const rawText = await res.text();
      console.log('[Claude] raw body:', rawText);
      if (!res.ok) throw new Error(`API ${res.status}`);
      const json = JSON.parse(rawText);
      const cleaned = json.content[0].text.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '').trim();
      const parsed = JSON.parse(cleaned);
      console.log('[Claude] parsed:', parsed);
      const steps = (parsed.steps || []).map(s => ({
        title: s.title || '',
        body: s.body || '',
        image_prompt: s.image_prompt || '',
        image: false,
      }));
      const patch = {
        steps,
        thumbnail_prompt: parsed.thumbnail_prompt || '',
        status: a.status === 'new' ? 'draft' : a.status,
      };
      await supabase.from('articles').update({
        thumbnail_prompt: patch.thumbnail_prompt,
        status: patch.status,
      }).eq('id', id);
      await supabase.from('steps').upsert(
        steps.map((s, i) => ({
          article_id: id,
          step_number: i + 1,
          title: s.title,
          body: s.body,
          image_prompt: s.image_prompt,
        })),
        { onConflict: 'article_id,step_number' }
      );
      updateArticle(id, patch);
      showToast('Article generated');
    } catch (err) {
      console.error('Generate failed:', err);
      showToast('Generation failed');
    }
  };

  const handleGenerateImage = async (id, slot) => {
    if (imagesUsed >= imagesQuota) return;
    const a = articles.find(x => x.id === id);
    if (!a) return;

    const isThumb = slot === 'thumb';
    const prompt = isThumb ? a.thumbnail_prompt : a.steps[slot]?.image_prompt;
    if (!prompt) { showToast('No prompt — generate text first'); return; }

    const aspectRatio = isThumb ? '1:1' : '4:3';
    const path = isThumb
      ? `${a.slug}/thumbnail.png`
      : `${a.slug}/step-${String(slot + 1).padStart(2, '0')}.png`;

    showToast('Generating image…');
    try {
      const res = await fetch(
        `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent?key=${CONFIG.GEMINI_API_KEY}`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            contents: [{
              parts: STYLE_REFERENCE_IMAGES.length > 0
                ? [
                    ...STYLE_REFERENCE_IMAGES.map(img => ({ inline_data: { mime_type: img.mimeType, data: img.data } })),
                    { text: STYLE_PREFIX + prompt },
                  ]
                : [{ text: STYLE_PREFIX + prompt }],
            }],
            generationConfig: { responseModalities: ['TEXT', 'IMAGE'] },
          }),
        }
      );
      if (!res.ok) {
        const errText = await res.text();
        throw new Error(`Gemini ${res.status}: ${errText}`);
      }
      const json = await res.json();
      const imagePart = json.candidates[0].content.parts.find(p => p.inlineData);
      if (!imagePart) throw new Error('No image in Gemini response');
      const b64 = imagePart.inlineData.data;
      const mimeType = imagePart.inlineData.mimeType || 'image/png';
      const byteChars = atob(b64);
      const byteArray = new Uint8Array(byteChars.length);
      for (let i = 0; i < byteChars.length; i++) byteArray[i] = byteChars.charCodeAt(i);
      const blob = new Blob([byteArray], { type: mimeType });

      const { error: uploadError } = await supabase.storage
        .from('article-images')
        .upload(path, blob, { contentType: mimeType, upsert: true });
      if (uploadError) throw uploadError;

      const { data: urlData } = supabase.storage.from('article-images').getPublicUrl(path);
      const publicUrl = urlData.publicUrl;

      if (isThumb) {
        await supabase.from('articles').update({ thumbnail_url: publicUrl }).eq('id', id);
        updateArticle(id, { thumbnail_image: true, thumbnail_url: publicUrl });
      } else {
        await supabase.from('steps')
          .update({ image_url: publicUrl })
          .eq('article_id', id)
          .eq('step_number', slot + 1);
        updateStep(id, slot, { image: true, image_url: publicUrl });
      }

      setImagesUsed(n => n + 1);
      showToast('Image generated');
    } catch (err) {
      console.error('Image generation failed:', err);
      showToast('Image generation failed');
    }
  };

  const handleNewArticle = ({ title, category }) => {
    const newId = `article-${String(articles.length + 1).padStart(2, '0')}`;
    const newArticle = {
      id: newId, slug: newId, title, category,
      reading_time: '2 min', creative_count: 0, status: 'new',
      updated_at: 'just now',
      thumbnail_prompt: '', thumbnail_image: false, steps: [],
    };
    setArticles(prev => [newArticle, ...prev]);
    setShowNewModal(false);
    setView({ name: 'detail', id: newId });
  };

  const handleDelete = (id) => {
    if (!window.confirm('Delete this article? This cannot be undone.')) return;
    setArticles(prev => prev.filter(a => a.id !== id));
    setView({ name: 'dashboard' });
    showToast('Article deleted');
  };

  const exportArticles = async (articleIds, includeImages) => {
    showToast(includeImages ? 'Building ZIP…' : 'Building JSON…');
    try {
      const { data, error } = await supabase
        .from('articles')
        .select('*, steps(*)')
        .in('id', articleIds);
      if (error) throw error;

      const articlesJson = (data || []).map(a => ({
        id: a.slug,
        title: a.title,
        category: a.category,
        reading_time: a.reading_time,
        thumbnail: a.thumbnail_url ? `${a.slug}/thumbnail.png` : null,
        steps: (a.steps || [])
          .sort((x, y) => x.step_number - y.step_number)
          .map(s => ({
            step_number: s.step_number,
            title: s.title || '',
            body: s.body || '',
            image: s.image_url ? `${a.slug}/step-${String(s.step_number).padStart(2, '0')}.png` : null,
          })),
      }));

      if (!includeImages) {
        const blob = new Blob([JSON.stringify(articlesJson, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url; a.download = 'articles.json'; a.click();
        URL.revokeObjectURL(url);
        showToast('Downloaded articles.json');
        return;
      }

      const zip = new JSZip();
      zip.file('articles.json', JSON.stringify(articlesJson, null, 2));

      for (const article of (data || [])) {
        if (article.thumbnail_url) {
          const res = await fetch(article.thumbnail_url);
          if (res.ok) zip.file(`${article.slug}/thumbnail.png`, await res.blob());
        }
        for (const step of (article.steps || [])) {
          if (step.image_url) {
            const res = await fetch(step.image_url);
            if (res.ok) zip.file(
              `${article.slug}/step-${String(step.step_number).padStart(2, '0')}.png`,
              await res.blob()
            );
          }
        }
      }

      const zipBlob = await zip.generateAsync({ type: 'blob' });
      const url = URL.createObjectURL(zipBlob);
      const a = document.createElement('a');
      a.href = url; a.download = 'kidden-studio-export.zip'; a.click();
      URL.revokeObjectURL(url);
      showToast('Downloaded export ZIP');
    } catch (err) {
      console.error('Export failed:', err);
      showToast('Export failed');
    }
  };

  const handleOpenAndGenerate = (id) => {
    setView({ name: 'detail', id });
    setTimeout(() => handleGenerateText(id), 80);
  };


  const currentArticle = view.name !== 'dashboard' ? articles.find(a => a.id === view.id) : null;

  if (loading) return <div className="app-loading">Loading…</div>;

  return (
    <div className="app">
      <Sidebar
        articles={articles}
        currentNav={navView}
        onNav={(id) => { setNavView(id); setView({ name: 'dashboard' }); }}
        onNewArticle={() => setShowNewModal(true)}
        imagesUsed={imagesUsed}
        imagesQuota={imagesQuota}
        styleRefCount={styleRefCount}
        onSetStyleReference={() => loadStyleReference((count) => setStyleRefCount(count))}
      />
      <main className="main">
        {view.name === 'dashboard' && navView === 'home' && (
          <Home
            articles={articles}
            onOpenAndGenerate={handleOpenAndGenerate}
            onOpen={(id) => setView({ name: 'detail', id })}
            onNewCustom={() => setShowNewModal(true)}
          />
        )}
        {view.name === 'dashboard' && navView !== 'home' && (
          <Dashboard
            articles={articles}
            navView={navView}
            onNew={() => setShowNewModal(true)}
            onOpen={(id) => setView({ name: 'detail', id })}
            onImport={() => showToast('CSV import — coming in v1.1')}
            onDelete={handleDelete}
            onExport={exportArticles}
          />
        )}
        {view.name === 'detail' && currentArticle && (
          <Detail
            article={currentArticle}
            saveState={saveState}
            imageQuotaLeft={imagesQuota - imagesUsed}
            onBack={() => setView({ name: 'dashboard' })}
            onUpdateArticle={(patch) => updateArticle(currentArticle.id, patch)}
            onUpdateStep={(idx, patch) => updateStep(currentArticle.id, idx, patch)}
            onPreview={() => setView({ name: 'preview', id: currentArticle.id })}
            onDelete={() => handleDelete(currentArticle.id)}
            onGenerateText={() => handleGenerateText(currentArticle.id)}
            onCopy={handleCopyPrompt}
            onGenerateImage={(slot) => handleGenerateImage(currentArticle.id, slot)}
            onExport={() => exportArticles([currentArticle.id], true)}
          />
        )}
        {view.name === 'preview' && currentArticle && (
          <Preview
            article={currentArticle}
            onClose={() => setView({ name: 'detail', id: currentArticle.id })}
          />
        )}
      </main>

      {showNewModal && <NewArticleModal onClose={() => setShowNewModal(false)} onCreate={handleNewArticle} />}
      <Toast msg={toast} />

      <window.TweaksPanel title="Tweaks">
        <window.TweakSection title="Brand">
          <window.TweakColor
            label="Accent color"
            value={tweaks.accent}
            options={['#0066FF', '#0e0a1f', '#1f9d55', '#e8542b', '#9333ea']}
            onChange={(v) => setTweak('accent', v)}
          />
        </window.TweakSection>
        <window.TweakSection title="Layout">
          <window.TweakRadio
            label="Corners"
            value={tweaks.rounded}
            options={[
              { value: 'sharp',  label: 'Sharp' },
              { value: 'medium', label: 'Medium' },
              { value: 'soft',   label: 'Soft' },
            ]}
            onChange={(v) => setTweak('rounded', v)}
          />
        </window.TweakSection>
      </window.TweaksPanel>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
