/**
 * WordPress Hosting Adapter
 *
 * Publishes a generated multi-page website to a self-hosted WordPress site via the
 * REST API v2. Unlike static-host adapters (Netlify, S3, SFTP) that upload files
 * verbatim, this adapter:
 *
 *   1. Uploads CSS, JS, images, and fonts to the WordPress Media Library.
 *   2. Creates (or updates) a WordPress Page for each HTML file, with asset
 *      references rewritten to point to the Media Library URLs.
 *   3. Stores WordPress page IDs in `connection.providerData.pageIds` so that
 *      re-deployments update existing pages instead of creating duplicates.
 *
 * Credentials come from the HostingConnection (per-admin, not platform-level):
 *   - connection.host       → WordPress site URL (e.g. https://myblog.com)
 *   - connection.username   → WordPress username
 *   - secret (decrypted)    → WordPress Application Password
 *
 * Fully additive — nothing in the deploy worker, bundler, or other adapters changes.
 */

import fs from 'fs';
import path from 'path';
import { HostingAdapter, DeployInput, DeployResult } from './types';

// ============================================
// CONSTANTS
// ============================================

const WP_TIMEOUT = 60000;         // 60s for deploy operations (media uploads can be large)
const WP_VALIDATE_TIMEOUT = 10000; // 10s for validate (quick check)

// ============================================
// HELPER: Authenticated fetch
// ============================================

function basicAuth(username: string, password: string): string {
  return 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64');
}

function siteUrl(connection: any): string {
  return String(connection.host || connection.baseUrl || '').replace(/\/+$/, '');
}

/**
 * Make an authenticated request to the WordPress REST API.
 * Returns parsed JSON on success, or throws with a descriptive error on failure.
 */
async function wpRequest(
  url: string,
  authHeader: string,
  options: { method?: string; body?: any; headers?: Record<string, string>; timeout?: number } = {},
): Promise<any> {
  const { method = 'GET', body, headers: extraHeaders = {}, timeout = WP_TIMEOUT } = options;

  const fetchHeaders: Record<string, string> = {
    'Authorization': authHeader,
    'Accept': 'application/json',
    ...extraHeaders,
  };

  if (body && method !== 'GET') {
    // Only set Content-Type for JSON bodies (not for multipart/form-data)
    if (!extraHeaders['Content-Type']?.includes('multipart')) {
      fetchHeaders['Content-Type'] = 'application/json';
    }
  }

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      method,
      headers: fetchHeaders,
      body: body && method !== 'GET' ? body : undefined,
      signal: controller.signal,
    });

    clearTimeout(timeoutId);

    if (!response.ok) {
      let errorDetail = `WordPress API returned ${response.status}`;
      try {
        const errBody: any = await response.json();
        if (errBody?.message) {
          errorDetail = errBody.message;
        } else if (errBody?.code) {
          errorDetail = `${errBody.code}: ${errBody.message || response.statusText}`;
        }
      } catch {
        errorDetail = response.statusText || errorDetail;
      }
      throw new Error(errorDetail);
    }

    // Some endpoints (e.g. DELETE) may return 200 with no body
    const text = await response.text();
    try {
      return JSON.parse(text);
    } catch {
      return { ok: true };
    }
  } catch (err: any) {
    clearTimeout(timeoutId);
    if (err.name === 'AbortError') {
      throw new Error('Request timed out — the WordPress site did not respond in time');
    }

    // Extract underlying cause (Node.js fetch wraps DNS/TLS/SSL errors in a
    // generic TypeError "fetch failed" — the real detail is in err.cause).
    const cause = err.cause;
    const causeMessage = cause?.message || cause?.code || '';
    const causeCode = cause?.code || '';

    if (err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' || causeCode === 'ENOTFOUND' || causeCode === 'EAI_AGAIN') {
      throw new Error('Unable to resolve the WordPress site URL — check the URL and try again');
    }
    if (err.code === 'ECONNREFUSED' || causeCode === 'ECONNREFUSED' || causeMessage.includes('ECONNREFUSED')) {
      throw new Error('Connection refused — the WordPress site is not accepting connections. Check the URL and port.');
    }
    if (causeMessage.includes('SSL') || causeMessage.includes('TLS') || causeMessage.includes('CERT') || causeCode === 'EPROTO' || causeMessage.includes('EPROTO')) {
      throw new Error(`SSL/TLS connection failed — the WordPress site may be down, not support HTTPS, or have an invalid certificate. Try accessing the URL in a browser first. (${causeMessage || causeCode})`);
    }
    if (err.code === 'ECONNRESET' || causeCode === 'ECONNRESET') {
      throw new Error('Connection was reset by the WordPress site — it may be overloaded or misconfigured');
    }
    if (err.code === 'ETIMEDOUT' || causeCode === 'ETIMEDOUT') {
      throw new Error('Connection timed out — the WordPress site did not respond in time');
    }

    // Fallback: include the underlying cause if available
    if (causeMessage) {
      throw new Error(`Unable to connect to WordPress site: ${err.message} (${causeMessage})`);
    }
    throw new Error(`Unable to connect to WordPress site: ${err.message}`);
  }
}

// ============================================
// HELPER: MIME type lookup (avoids mime-types dependency)
// ============================================

const MIME_MAP: Record<string, string> = {
  '.html': 'text/html',
  '.htm': 'text/html',
  '.css': 'text/css',
  '.js': 'application/javascript',
  '.json': 'application/json',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
  '.webp': 'image/webp',
  '.ico': 'image/x-icon',
  '.woff': 'font/woff',
  '.woff2': 'font/woff2',
  '.ttf': 'font/ttf',
  '.eot': 'application/vnd.ms-fontobject',
  '.otf': 'font/otf',
  '.pdf': 'application/pdf',
  '.txt': 'text/plain',
  '.xml': 'application/xml',
  '.zip': 'application/zip',
};

function lookupMimeType(filename: string): string {
  const ext = path.extname(filename).toLowerCase();
  return MIME_MAP[ext] || 'application/octet-stream';
}

// ============================================
// HELPER: Upload a file to WordPress Media Library
// ============================================

interface MediaUploadResult {
  id: number;
  source_url: string;
  title: { rendered: string };
  media_type: string;
  mime_type: string;
}

async function uploadMedia(
  siteUrlBase: string,
  authHeader: string,
  absPath: string,
  relPath: string,
): Promise<MediaUploadResult> {
  const fileContent = fs.readFileSync(absPath);
  const filename = path.basename(relPath);
  const mimeType = lookupMimeType(filename);

  const formData = new FormData();
  formData.append('file', new Blob([fileContent], { type: mimeType }), filename);

  const result = await wpRequest(`${siteUrlBase}/wp-json/wp/v2/media`, authHeader, {
    method: 'POST',
    body: formData,
    headers: {
      'Content-Type': 'multipart/form-data',
      // Let fetch set the boundary automatically
    },
    timeout: WP_TIMEOUT,
  });

  if (!result.id || !result.source_url) {
    throw new Error(`WordPress media upload failed for ${relPath}: unexpected response`);
  }

  return result as MediaUploadResult;
}

// ============================================
// HELPER: Create or update a WordPress Page
// ============================================

interface PageData {
  slug: string;
  title: string;
  content: string;
  status: 'publish' | 'draft';
  template?: string; // WordPress page template (e.g., 'blank', 'full-width'). Theme must support it.
}

async function upsertPage(
  siteUrlBase: string,
  authHeader: string,
  pageData: PageData,
  existingId?: number,
): Promise<{ id: number; link: string }> {
  if (existingId) {
    // Update existing page
    const payload: Record<string, any> = {
      title: pageData.title,
      content: pageData.content,
      status: pageData.status,
    };
    if (pageData.template) payload.template = pageData.template;
    const result = await wpRequest(`${siteUrlBase}/wp-json/wp/v2/pages/${existingId}`, authHeader, {
      method: 'PUT',
      body: JSON.stringify(payload),
    });
    return { id: result.id, link: result.link };
  }

  // Check if a page with this slug already exists
  const searchResult = await wpRequest(
    `${siteUrlBase}/wp-json/wp/v2/pages?slug=${encodeURIComponent(pageData.slug)}&status=publish,draft`,
    authHeader,
  );
  if (Array.isArray(searchResult) && searchResult.length > 0) {
    // Update the existing page
    const existing = searchResult[0];
    const payload: Record<string, any> = {
      title: pageData.title,
      content: pageData.content,
      status: pageData.status,
    };
    if (pageData.template) payload.template = pageData.template;
    const result = await wpRequest(`${siteUrlBase}/wp-json/wp/v2/pages/${existing.id}`, authHeader, {
      method: 'PUT',
      body: JSON.stringify(payload),
    });
    return { id: result.id, link: result.link };
  }

  // Create new page
  const result = await wpRequest(`${siteUrlBase}/wp-json/wp/v2/pages`, authHeader, {
    method: 'POST',
    body: JSON.stringify(pageData),
  });
  return { id: result.id, link: result.link };
}

// ============================================
// HELPER: Rewrite asset references in HTML
// ============================================

/**
 * Replace relative asset paths in HTML with absolute WordPress Media Library URLs.
 * Also replaces /uploads/... references and images/... references that the bundler
 * may have inserted.
 */
function rewriteAssetReferences(
  html: string,
  assetUrlMap: Map<string, string>,
  htmlRelPath: string,
): string {
  let result = html;

  for (const [relPath, mediaUrl] of assetUrlMap) {
    // Replace exact relative paths (e.g., "css/styles.css", "images/photo.png")
    // Need to account for different depths
    const depth = htmlRelPath.split('/').length - 1; // 0 for root, 1 for sub/
    const prefix = '../'.repeat(depth);

    // Try with depth prefix (for subfolder pages)
    if (prefix && result.includes(prefix + relPath)) {
      result = result.split(prefix + relPath).join(mediaUrl);
    }

    // Try without prefix (for root-level pages)
    if (result.includes(relPath)) {
      result = result.split(relPath).join(mediaUrl);
    }
  }

  return result;
}

// ============================================
// CSS overrides for full-width landing pages in WordPress themes
// ============================================

/**
 * CSS overrides injected into WordPress page content to ensure landing pages
 * display at full viewport width instead of being constrained by theme containers.
 *
 * WordPress themes wrap page content in containers with max-width constraints
 * (e.g., .entry-content at 800px, .wp-block-post-content at theme.json contentSize).
 * These overrides reset those constraints so the landing page fills the entire width.
 *
 * The .mengo-landing-page wrapper isolates these overrides so they don't affect
 * the theme's header, footer, or sidebar elements outside the landing page content.
 *
 * NOTE: This CSS is ONLY injected when deploying to WordPress — other adapters
 * (Netlify, Vercel, S3, FTP, etc.) upload the full HTML as-is and are unaffected.
 */
const WORDPRESS_FULL_WIDTH_CSS = `<style>header.wp-block-template-part,footer.wp-block-template-part,.wp-block-site-title,.wp-block-navigation,.wp-block-navigation-item,.site-header,.site-footer,.site-branding,.main-navigation,.header-section,.footer-section,nav.main-navigation{display:none !important}.mengo-landing-page{width:100vw !important;max-width:100vw !important;margin-left:calc(-50vw + 50%) !important;padding:0 !important;overflow-x:hidden !important}.is-layout-constrained>.mengo-landing-page,.entry-content>.mengo-landing-page,.wp-block-post-content>.mengo-landing-page,.site-content>.mengo-landing-page,.page-content>.mengo-landing-page,article>.mengo-landing-page{max-width:none !important;width:100% !important;margin:0 !important;padding:0 !important}</style>`;

// ============================================
// HELPER: Minify a <style> tag for WordPress compatibility
// ============================================

/**
 * Minify a <style>...</style> tag by collapsing its CSS into a single line.
 *
 * WordPress's wpautop filter runs on page content and inserts <p> tags around
 * double-newlines and wraps loose text in paragraphs. If a <style> block contains
 * newlines (which all formatted CSS does), wpautop inserts </p><p> inside it,
 * breaking the CSS selectors and rules.
 *
 * Minifying the CSS (removing newlines and collapsing whitespace) prevents wpautop
 * from corrupting it, since there are no double-newlines to trigger paragraph insertion.
 *
 * Example input:
 *   <style>
 *     .hero { color: red; }
 *     .btn { background: blue; }
 *   </style>
 *
 * Example output:
 *   <style>.hero{color:red}.btn{background:blue}</style>
 */
function minifyStyleTag(styleTag: string): string {
  return styleTag.replace(/<style([^>]*)>([\s\S]*?)<\/style>/gi, (_match, attrs: string, css: string) => {
    // Collapse CSS: remove newlines, collapse whitespace, remove spaces around { } : ;
    const minifiedCss = css
      .replace(/\/\*[\s\S]*?\*\//g, '')     // Remove CSS comments (/* ... */)
      .replace(/\s+/g, ' ')                    // Collapse all whitespace to single space
      .replace(/\s*([{}:;,])\s*/g, '$1')      // Remove spaces around { } : ; ,
      .replace(/;\s*}/g, '}')                  // Remove trailing semicolons before }
      .replace(/\s*>\s*/g, '>')               // Remove spaces around > (child combinator)
      .trim();
    return `<style${attrs}>${minifiedCss}</style>`;
  });
}

// ============================================
// HELPER: Extract body content from full HTML
// ============================================

/**
 * Extract the content from the <body> tag of an HTML document for injection
 * into a WordPress page.
 *
 * WordPress wraps page content in its theme (adding <html>, <head>, <body>), so
 * we cannot inject a full HTML document. We must extract only the meaningful content.
 *
 * This function:
 *   1. Extracts <style> blocks from the full HTML (they contain the landing page CSS)
 *   2. Extracts <link rel="stylesheet"> tags (e.g., Google Fonts)
 *   3. Extracts the <body> inner content
 *   4. Wraps body content in a .mengo-landing-page div (isolates CSS from theme)
 *   5. Prepends WordPress full-width CSS overrides (breaks out of theme containers)
 *   6. Returns: full-width CSS + styles/links + wrapped body content
 *
 * NOTE: This function is ONLY called by the WordPress adapter. Other hosting adapters
 * (Netlify, Vercel, S3, FTP, etc.) upload the full HTML as-is and are unaffected.
 */
function extractBodyContent(html: string): string {
  // Extract <style> blocks from the full HTML — these contain the landing page CSS
  const headStyles: string[] = [];
  const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
  let styleMatch;
  while ((styleMatch = styleRegex.exec(html)) !== null) {
    // Minify the CSS inside <style> tags to prevent WordPress's wpautop from
    // inserting <p> tags inside the style block (which breaks the CSS).
    // wpautop adds <p></p> around double-newlines and wraps text nodes in <p>,
    // but minified CSS is a single line with no newlines, so it's left intact.
    const minified = minifyStyleTag(styleMatch[0]);
    headStyles.push(minified);
  }

  // Extract <link rel="stylesheet"> tags (e.g., Google Fonts)
  const headLinks: string[] = [];
  const linkRegex = /<link[^>]+rel=["']stylesheet["'][^>]*>/gi;
  let linkMatch;
  while ((linkMatch = linkRegex.exec(html)) !== null) {
    headLinks.push(linkMatch[0]);
  }

  // Try to extract content between <body> and </body>
  const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
  let bodyContent: string;

  if (bodyMatch) {
    bodyContent = bodyMatch[1].trim();
  } else {
    // Fallback: if no <body> tag, return the whole thing
    // (strip doctype and html/head if present, keep everything else)
    bodyContent = html
      .replace(/<!DOCTYPE[^>]*>/i, '')
      .replace(/<html[^>]*>/i, '')
      .replace(/<\/html>/i, '');

    // Remove <head> section from content (we already extracted styles/links above)
    bodyContent = bodyContent.replace(/<head[^>]*>[\s\S]*?<\/head>/i, '');
    bodyContent = bodyContent.replace(/<\/?html[^>]*>/gi, '').trim();
  }

  // Wrap body content in a unique container div.
  // This isolates the landing page CSS from the WordPress theme's header/footer/sidebar
  // so selectors like .container, .section, .hero only match within the landing page.
  const wrappedContent = `<div class="mengo-landing-page">\n${bodyContent}\n</div>`;

  // Combine: full-width overrides first, then head styles/links, then wrapped body content
  const headElements = [...headStyles, ...headLinks];
  const parts = [WORDPRESS_FULL_WIDTH_CSS, ...headElements, wrappedContent];

  return parts.join('\n');
}

// ============================================
// HELPER: Derive slug from file path
// ============================================

function deriveSlug(relPath: string): string {
  // index.html → homepage slug
  // about.html → about
  // sub/contact.html → contact
  // directory/index.html → directory
  let slug = relPath
    .replace(/\.html?$/i, '')        // Remove .html/.htm extension
    .replace(/\\/g, '/');             // Normalize path separators

  // Remove trailing /index
  if (slug.endsWith('/index')) {
    slug = slug.slice(0, -6) || '/';
  }

  // Remove leading ./ or /
  slug = slug.replace(/^\.\//, '').replace(/^\//, '');

  // Fallback
  if (!slug) slug = 'home';

  return slug;
}

// ============================================
// HELPER: Derive title from slug
// ============================================

function deriveTitle(relPath: string): string {
  const slug = deriveSlug(relPath);
  if (slug === '/' || slug === '' || slug === 'home') return 'Home';
  // Capitalize first letter, replace hyphens with spaces
  return slug
    .split(/[-_/]/)
    .map((part: string) => part.charAt(0).toUpperCase() + part.slice(1))
    .join(' ');
}

// ============================================
// HELPER: Set WordPress homepage as a static page
// ============================================

/**
 * Configure WordPress to show a specific Page as the site homepage
 * instead of the default blog posts list.
 *
 * Calls the WordPress Settings API:
 *   - show_on_front: 'page'  (instead of 'posts')
 *   - page_on_front: <pageId>  (the page to show at /)
 *   - page_for_posts: <blogPageId> (optional; creates "Blog" if not set)
 *
 * This is necessary because WordPress defaults to showing blog posts
 * on the homepage, so a freshly deployed landing page would only be
 * visible at its slug URL (e.g., /home/) — not at the site root (/).
 */
async function setHomepage(
  siteUrlBase: string,
  authHeader: string,
  homepagePageId: number,
): Promise<boolean> {
  // First, check if there's already a "Blog" / "Posts" page for the blog.
  // If not, create one so WordPress has somewhere to put the blog.
  let blogPageId: number | undefined;

  try {
    const blogSearch = await wpRequest(
      `${siteUrlBase}/wp-json/wp/v2/pages?slug=blog&status=publish,draft`,
      authHeader,
    );

    if (Array.isArray(blogSearch) && blogSearch.length > 0) {
      blogPageId = blogSearch[0].id;
    }
  } catch {
    // Non-critical — if we can't search, we'll just skip page_for_posts
  }

  // Create a "Blog" page if none exists (WordPress needs one for the posts page)
  if (!blogPageId) {
    try {
      const blogPage = await wpRequest(`${siteUrlBase}/wp-json/wp/v2/pages`, authHeader, {
        method: 'POST',
        body: JSON.stringify({
          title: 'Blog',
          slug: 'blog',
          status: 'publish',
          content: '',
        }),
      });
      blogPageId = blogPage.id;
      console.log(`[WordPress Adapter] Created Blog page (ID: ${blogPageId}) for posts`);
    } catch {
      // If we can't create the blog page, proceed without it
      console.warn('[WordPress Adapter] Could not create Blog page — posts page will not be set');
    }
  }

  // Build the settings payload
  const settingsPayload: Record<string, any> = {
    show_on_front: 'page',
    page_on_front: homepagePageId,
  };

  if (blogPageId) {
    settingsPayload.page_for_posts = blogPageId;
  }

  // Apply the settings via the WordPress REST API
  try {
    await wpRequest(`${siteUrlBase}/wp-json/wp/v2/settings`, authHeader, {
      method: 'POST',
      body: JSON.stringify(settingsPayload),
    });
    console.log(`[WordPress Adapter] Set page ${homepagePageId} as static homepage${blogPageId ? `, page ${blogPageId} as posts page` : ''}`);
    return true;
  } catch (err: any) {
    // Non-fatal — the page is still published, just not set as the homepage.
    // Some WordPress setups may restrict the Settings API.
    console.warn(`[WordPress Adapter] Could not set homepage via Settings API: ${err?.message}`);
    console.warn('[WordPress Adapter] The landing page is published but may not appear at the site root. Manually set it in WordPress → Settings → Reading.');
    return false;
  }
}

// ============================================
// ADAPTER: validate
// ============================================

async function validate(
  connection: any,
  secret: string,
): Promise<{ ok: boolean; error?: string }> {
  const url = siteUrl(connection);
  const username = connection.username || '';
  const appPassword = secret || '';

  if (!url || !username || !appPassword) {
    return { ok: false, error: 'Site URL, username, and application password are required' };
  }

  try {
    const auth = basicAuth(username, appPassword);
    await wpRequest(`${url}/wp-json/wp/v2/users/me`, auth, {
      method: 'GET',
      timeout: WP_VALIDATE_TIMEOUT,
    });
    return { ok: true };
  } catch (err: any) {
    // wpRequest now extracts err.cause and throws descriptive errors,
    // so err.message will already contain the underlying cause detail.
    return { ok: false, error: err?.message || 'WordPress connection failed' };
  }
}

// ============================================
// ADAPTER: deploy
// ============================================

async function deploy(input: DeployInput): Promise<DeployResult> {
  const { bundle, connection, secret, page } = input;
  const url = siteUrl(connection);
  const username = connection.username || '';
  const appPassword = secret || '';

  if (!url) throw new Error('WordPress Site URL is required (set in the "Host" field of the connection).');
  if (!username) throw new Error('WordPress username is required.');
  if (!appPassword) throw new Error('WordPress Application Password is required.');

  const auth = basicAuth(username, appPassword);

  // ---------------------------------------------------------------
  // Pre-check: Verify the WordPress site is reachable before deploying
  // ---------------------------------------------------------------
  console.log(`[WordPress Adapter] Verifying connectivity to ${url}...`);
  try {
    await wpRequest(`${url}/wp-json/wp/v2/users/me`, auth, {
      method: 'GET',
      timeout: WP_VALIDATE_TIMEOUT,
    });
    console.log(`[WordPress Adapter] Connectivity verified — site is reachable.`);
  } catch (err: any) {
    // Re-throw with a clear pre-deploy message
    throw new Error(`WordPress site is not reachable — deployment aborted. ${err.message}`);
  }

  // Existing page IDs for re-deployment (stored in connection.providerData)
  const existingPageIds: Record<string, number> = (connection as any).providerData?.pageIds || {};

  console.log(`[WordPress Adapter] Starting deployment to ${url} (${bundle.fileCount} files)`);

  // ---------------------------------------------------------------
  // Step 1: Upload all non-HTML assets to the WordPress Media Library
  // ---------------------------------------------------------------
  const assetUrlMap = new Map<string, string>(); // relPath → source_url

  const nonHtmlFiles = bundle.files.filter(
    (f) => !f.relPath.toLowerCase().endsWith('.html') && !f.relPath.toLowerCase().endsWith('.htm'),
  );

  for (const file of nonHtmlFiles) {
    // Skip sitemap.xml and robots.txt — they don't belong in the Media Library
    if (file.relPath === 'sitemap.xml' || file.relPath === 'robots.txt') {
      continue;
    }

    try {
      const media = await uploadMedia(url, auth, file.absPath, file.relPath);
      assetUrlMap.set(file.relPath, media.source_url);
      console.log(`[WordPress Adapter] Uploaded ${file.relPath} → ${media.source_url}`);
    } catch (err: any) {
      console.warn(`[WordPress Adapter] Failed to upload ${file.relPath}: ${err?.message}`);
      // Continue — missing assets will keep their relative paths (broken on the WP site,
      // but the deploy doesn't fail entirely because of one asset).
    }
  }

  // ---------------------------------------------------------------
  // Step 2: Create/update WordPress pages from HTML files
  // ---------------------------------------------------------------
  const pageIds: Record<string, number> = {};
  let homepageLink = '';

  const htmlFiles = bundle.files.filter(
    (f) => f.relPath.toLowerCase().endsWith('.html') || f.relPath.toLowerCase().endsWith('.htm'),
  );

  for (const file of htmlFiles) {
    try {
      // Read HTML content
      let html = fs.readFileSync(file.absPath, 'utf8');

      // Rewrite asset references to WordPress Media Library URLs
      html = rewriteAssetReferences(html, assetUrlMap, file.relPath);

      // Extract body content (WordPress wraps pages in its theme)
      const content = extractBodyContent(html);

      // Derive slug and title from the filename
      const slug = deriveSlug(file.relPath);
      const title = deriveTitle(file.relPath) || (page?.name || 'Website');

      // Look up existing page ID for this relPath
      const existingId = existingPageIds[file.relPath];

      // Create or update the WordPress page
      // Try with 'blank' template first (minimal theme wrapper, no header/footer/sidebar).
      // If the theme doesn't support 'blank', WordPress returns a 400 error — we catch
      // that and retry without a template (falls back to theme default).
      let result: { id: number; link: string };
      try {
        result = await upsertPage(url, auth, {
          slug,
          title,
          content,
          status: 'publish',
          template: 'blank',
        }, existingId);
      } catch (templateErr: any) {
        // If the error is about an invalid template, retry without specifying a template.
        // The theme will use its default template, and our CSS overrides handle the width.
        if (templateErr?.message?.includes('template') || templateErr?.message?.includes('Invalid param')) {
          console.warn(`[WordPress Adapter] Theme does not support 'blank' template, using default template`);
          result = await upsertPage(url, auth, {
            slug,
            title,
            content,
            status: 'publish',
          }, existingId);
        } else {
          throw templateErr;
        }
      }

      pageIds[file.relPath] = result.id;
      console.log(`[WordPress Adapter] ${existingId ? 'Updated' : 'Created'} page "${slug}" (ID: ${result.id}) → ${result.link}`);

      // Track the homepage URL
      if (file.relPath === 'index.html' || htmlFiles.length === 1) {
        homepageLink = result.link;
      }
    } catch (err: any) {
      console.error(`[WordPress Adapter] Failed to create/update page for ${file.relPath}: ${err?.message}`);
      throw new Error(`Failed to publish page "${file.relPath}": ${err?.message}`);
    }
  }

  // ---------------------------------------------------------------
  // Step 3: Set the homepage page as WordPress's static front page
  // ---------------------------------------------------------------
  // The index.html page (or the single HTML file) is the homepage.
  // Configure WordPress Reading Settings so it shows at the site root
  // instead of the default blog posts list.
  let homepageSet = false;
  const homepagePageId = pageIds['index.html'] || Object.values(pageIds)[0];
  if (homepagePageId) {
    homepageSet = await setHomepage(url, auth, homepagePageId);
  }

  // ---------------------------------------------------------------
  // Step 4: Return deploy result
  // ---------------------------------------------------------------
  // When the homepage is successfully set as the static front page,
  // the site root URL (/) will display it, so return the site root.
  // Otherwise fall back to the page's own URL (e.g. /home/).
  const deployUrl = homepageSet
    ? (url.endsWith('/') ? url : url + '/')
    : (homepageLink || (url.endsWith('/') ? url : url + '/'));
  const connectionPatch: Record<string, any> = {};
  if (Object.keys(pageIds).length > 0) {
    connectionPatch.providerData = { ...(connection as any).providerData || {}, pageIds };
  }

  console.log(`[WordPress Adapter] Deployment complete → ${deployUrl}`);

  return {
    deployUrl,
    providerDeployId: bundle.hash,
    connectionPatch: Object.keys(connectionPatch).length > 0 ? connectionPatch : undefined,
  };
}

// ============================================
// EXPORT
// ============================================

export const wordpressAdapter: HostingAdapter = {
  provider: 'wordpress',
  deploy,
  validate,
};