import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { deriveVariables, type WorkflowVariable } from '../utils/workflowTemplate'; export interface Workflow { id: string; title: string; description?: string; template: string; variables: WorkflowVariable[]; scope: 'builtin' | 'user'; pinned?: boolean; useCount?: number; } export interface WorkflowInput { title: string; description?: string; template: string; variables?: WorkflowVariable[]; } interface WorkflowsContextValue { workflows: Workflow[]; createWorkflow: (input: WorkflowInput) => Workflow; updateWorkflow: (id: string, input: WorkflowInput) => void; deleteWorkflow: (id: string) => void; duplicateWorkflow: (id: string) => Workflow | null; togglePin: (id: string) => void; recordUse: (id: string) => void; } const STORAGE_KEY = 'wpa_workflows'; // Seed built-ins: best-practice, step-by-step prompts with fill-in {{variables}}. // They describe the *steps* a common WordPress task should follow (the backend // skills supply the actual tooling), and leave the unique bits as blanks. type BuiltinSeed = Omit & { variables?: WorkflowVariable[] }; const BUILTIN_SEEDS: BuiltinSeed[] = [ { id: 'bi_blog_post', scope: 'builtin', pinned: true, title: 'Write a new blog post', description: 'Research-backed draft with SEO, fields & featured image', template: `Write a complete, publish-ready blog post for my WordPress site about {{topic}}. Before writing: - Review the existing posts on my site and confirm this topic isn't already covered. If something similar exists, tell me and suggest a fresh angle before continuing. - Match the tone, structure, and formatting of my existing published posts. The post should: - Target the keyword "{{target_keyword}}" and be written for {{audience}}. - Open with a short, engaging intro, use a clear H2/H3 heading structure, and end with a concise conclusion or call to action. - Be roughly {{word_count}} words, original, and free of filler or repetition. Set it up as a draft (do not publish) and fill everything in: - Title, slug, and excerpt. - All custom fields the post type uses (this site uses ACF) — leave a note for any field you can't confidently fill. - A relevant featured image sourced from Pexels that matches the topic, with descriptive alt text. For SEO: set a focus keyword, an SEO title under ~60 characters, and a meta description under ~155 characters, and suggest 2–3 internal links to related posts on my site. When you're done, summarize what you created and flag anything you need me to review.`, variables: [ { key: 'audience', label: 'Audience', default: 'a general audience' }, { key: 'word_count', label: 'Word count', default: '800–1,200' }, ], }, { id: 'bi_seo_optimize', scope: 'builtin', pinned: true, title: 'Optimize a page for SEO', description: 'On-page SEO audit and fixes for one URL', template: `Audit and improve the on-page SEO for {{page_url}} on my site, targeting the keyword "{{target_keyword}}". First, review the page's current title tag, meta description, URL/slug, heading structure, image alt text, and internal links, and compare them against the keyword intent and the rest of my content. Identify concrete problems — weak or missing meta description, a vague title, no H1 or multiple H1s, thin content, missing alt text, too few internal links, or the keyword not used naturally. Then apply safe improvements as a draft where edits are needed: - Rewrite the SEO title (~60 chars) and meta description (~155 chars). - Fix the heading structure so there's one clear H1 and a logical H2/H3 flow. - Add descriptive alt text to images missing it. - Suggest 3–5 internal links between this page and related content. Show me a before/after summary and list anything that needs my approval before it goes live.`, variables: [{ key: 'page_url', label: 'Page URL' }], }, { id: 'bi_refresh_post', scope: 'builtin', pinned: true, title: 'Refresh an outdated post', description: 'Modernize and re-optimize older content', template: `Help me refresh and update this existing post: {{page_url}}. - Review the current content and flag anything outdated, inaccurate, or thin. - Check whether it still matches search intent for "{{target_keyword}}" and how it compares to my newer content. - Update facts, examples, and statistics; tighten the intro; improve the heading structure; and expand any sections that are too shallow. - Refresh the SEO title and meta description, and add internal links to relevant newer posts on my site. - If the featured image looks dated, replace it with a relevant Pexels image and good alt text. Make all changes as a draft revision (don't touch the live post) and give me a summary of what changed and why.`, variables: [{ key: 'page_url', label: 'Page URL' }], }, { id: 'bi_health_check', scope: 'builtin', pinned: true, title: 'Run a site health check', description: 'Triage errors, warnings, and urgent fixes', template: `Do a health check on my WordPress site and tell me what needs attention, most urgent first. Look across: - Site Health warnings and critical issues. - Outdated, duplicate, or inactive plugins and themes that matter right now. - Overdue scheduled tasks, caching problems, and anything that could cause stale or broken pages. - Recent errors in the logs, if log sharing is enabled. For each finding, give me, in plain English: what's wrong and why it matters, how risky it is to leave it, and the recommended fix — including whether it's something you can safely do now or something I should approve first. Don't change anything yet. Start with the prioritized summary.`, }, { id: 'bi_broken_links', scope: 'builtin', title: 'Find and fix broken links', description: 'Scan for broken links and 404s, then fix', template: `Find broken links and 404 errors on my site and help me fix them. - Check my published pages and posts for links pointing to missing, moved, or error-returning content ({{scope}}). - For each broken link, tell me where it appears and what it most likely should point to instead. Where the fix is obvious — a renamed slug, a moved page, or a permalink/rewrite problem — prepare the correction as a draft and show it to me. Group anything ambiguous into a short list I can decide on. If this looks like a site-wide permalink issue, say so and recommend the safe fix first.`, variables: [{ key: 'scope', label: 'Scope', default: 'both internal and external links' }], }, { id: 'bi_clone_page', scope: 'builtin', title: 'Create a page like an existing one', description: "Reuse your best page's layout for new content", template: `Create a new {{page_type}} page for my site that reuses the design of my existing pages. - Look at my current published pages and pick the one whose layout best fits this purpose. - Clone that layout as a new draft so the live page is never touched. - Replace the copy, headings, images, and links with content for: {{page_goal}}. - Keep the styling, spacing, and section rhythm consistent with the source page. - Add a sensible title, slug, SEO title, and meta description. Leave it as a draft and walk me through what you changed so I can review before publishing.`, variables: [{ key: 'page_type', label: 'Page type', default: 'service' }], }, { id: 'bi_product_description', scope: 'builtin', title: 'Write a product description', description: 'Persuasive, SEO-aware WooCommerce copy', template: `Write a compelling product description for "{{product}}" in my WooCommerce store. - First, check how my existing products are described and match that voice and length. - Write a short, benefit-led summary plus a longer description covering features, use cases, and what makes it worth buying. - Highlight these key points if they apply: {{key_details}}. - Naturally include the keyword "{{target_keyword}}", and write a meta description under ~155 characters. Save it to the product as a draft change for my review. Don't change price, inventory, or any other store settings.`, }, { id: 'bi_alt_text', scope: 'builtin', title: 'Add missing image alt text', description: 'Improve accessibility and image SEO', template: `Improve the image alt text across my site for accessibility and SEO. - Find images in my media library and on {{scope}} that are missing alt text or have unhelpful alt text (like a raw file name). - For each one, write concise, descriptive alt text based on what the image shows and the context of the page it appears on. - Skip purely decorative images and list them separately. Prepare the updates for my review, and tell me how many images you found and changed. Don't modify anything other than alt text.`, variables: [{ key: 'scope', label: 'Scope', default: 'my published pages and posts' }], }, ]; const BUILTINS: Workflow[] = BUILTIN_SEEDS.map(({ variables, ...workflow }) => ({ ...workflow, variables: deriveVariables(workflow.template, variables ?? []), })); type WorkflowMeta = { pinned?: boolean; useCount?: number }; interface PersistedState { user: Workflow[]; meta: Record; } const readPersisted = (): PersistedState => { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return { user: [], meta: {} }; const parsed = JSON.parse(raw) as Partial; return { user: parsed.user ?? [], meta: parsed.meta ?? {} }; } catch { return { user: [], meta: {} }; } }; const WorkflowsContext = createContext(null); export const WorkflowsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const initial = useMemo(readPersisted, []); const [userWorkflows, setUserWorkflows] = useState(initial.user); const [meta, setMeta] = useState>(initial.meta); useEffect(() => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify({ user: userWorkflows, meta })); } catch { // ignore storage failures } }, [userWorkflows, meta]); const workflows = useMemo(() => ( [...BUILTINS, ...userWorkflows].map((workflow) => ({ ...workflow, pinned: meta[workflow.id]?.pinned ?? workflow.pinned ?? false, useCount: meta[workflow.id]?.useCount ?? workflow.useCount ?? 0, })) ), [userWorkflows, meta]); const createWorkflow = useCallback((input: WorkflowInput): Workflow => { const workflow: Workflow = { id: uuidv4(), scope: 'user', title: input.title, description: input.description, template: input.template, variables: deriveVariables(input.template, input.variables), }; setUserWorkflows((prev) => [workflow, ...prev]); return workflow; }, []); const updateWorkflow = useCallback((id: string, input: WorkflowInput) => { setUserWorkflows((prev) => prev.map((workflow) => ( workflow.id === id ? { ...workflow, title: input.title, description: input.description, template: input.template, variables: deriveVariables(input.template, input.variables ?? workflow.variables), } : workflow ))); }, []); const deleteWorkflow = useCallback((id: string) => { setUserWorkflows((prev) => prev.filter((workflow) => workflow.id !== id)); setMeta((prev) => { if (!(id in prev)) return prev; const next = { ...prev }; delete next[id]; return next; }); }, []); const togglePin = useCallback((id: string) => { setMeta((prev) => { const current = prev[id]?.pinned ?? BUILTINS.find((w) => w.id === id)?.pinned ?? false; return { ...prev, [id]: { ...prev[id], pinned: !current } }; }); }, []); const recordUse = useCallback((id: string) => { setMeta((prev) => ({ ...prev, [id]: { ...prev[id], useCount: (prev[id]?.useCount ?? 0) + 1 } })); }, []); const duplicateWorkflow = useCallback((id: string): Workflow | null => { const source = [...BUILTINS, ...userWorkflows].find((workflow) => workflow.id === id); if (!source) return null; const copy: Workflow = { id: uuidv4(), scope: 'user', title: `${source.title} (copy)`, description: source.description, template: source.template, variables: source.variables, }; setUserWorkflows((prev) => [copy, ...prev]); return copy; }, [userWorkflows]); const value = useMemo(() => ({ workflows, createWorkflow, updateWorkflow, deleteWorkflow, duplicateWorkflow, togglePin, recordUse, }), [workflows, createWorkflow, updateWorkflow, deleteWorkflow, duplicateWorkflow, togglePin, recordUse]); return {children}; }; export const useWorkflows = (): WorkflowsContextValue => { const context = useContext(WorkflowsContext); if (!context) { throw new Error('useWorkflows must be used within a WorkflowsProvider'); } return context; };