<?php
declare(strict_types=1);

header("Content-Type: application/json; charset=utf-8");
ini_set("display_errors", "0");
error_reporting(E_ALL);

require_once __DIR__ . "/../app/db.php";

/**
 * One-and-only JSON responder.
 */
function jacai_out(array $payload, int $code = 200): void {
  http_response_code($code);
  echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
  exit;
}

/**
 * Always return JSON on fatals too (prevents blank/non-JSON).
 */
register_shutdown_function(function () {
  $e = error_get_last();
  if ($e && in_array($e["type"], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
    jacai_out([
      "ok" => false,
      "error" => "PHP fatal: " . ($e["message"] ?? "unknown"),
      "file" => $e["file"] ?? "",
      "line" => $e["line"] ?? 0,
    ], 500);
  }
});

/**
 * If db.php doesn't define this, we define it here.
 * Base dir should be the project root (one level above /app and /api).
 */
if (!function_exists("smm_base_dir")) {
  function smm_base_dir(): string {
    $root = realpath(__DIR__ . "/..");
    return $root ?: (dirname(__DIR__));
  }
}

function smm_get_tool(PDO $pdo, string $toolKey): ?array {
  $st = $pdo->prepare("SELECT * FROM tools WHERE key=? LIMIT 1");
  $st->execute([$toolKey]);
  $t = $st->fetch(PDO::FETCH_ASSOC);
  return $t ?: null;
}

function smm_cfg(array $tool): array {
  $cfg = $tool["config_json"] ?? "{}";
  $j = json_decode((string)$cfg, true);
  return is_array($j) ? $j : [];
}

function smm_render_placeholders($v, array $vars) {
  if (is_string($v)) {
    foreach ($vars as $k => $val) {
      $v = str_replace("{{".$k."}}", (string)$val, $v);
    }
    return $v;
  }
  if (is_array($v)) {
    $out = [];
    foreach ($v as $k => $vv) $out[$k] = smm_render_placeholders($vv, $vars);
    return $out;
  }
  return $v;
}

/**
 * Generic HTTP tool runner (uses tool.config_json).
 */
function smm_http_call(array $tool, array $vars): array {
  $cfg = smm_cfg($tool);

  $url     = (string)($cfg["url"] ?? "");
  $method  = strtoupper((string)($cfg["method"] ?? "POST"));
  $headers = $cfg["headers"] ?? [];
  $body    = $cfg["body_json"] ?? null;

  // api_key required for "http" runner in this design
  $api_key = (string)($cfg["api_key"] ?? "");
  if ($api_key === "") throw new Exception("Tool {$tool['key']} missing api_key in config_json");
  $vars["api_key"] = $api_key;

  $url     = smm_render_placeholders($url, $vars);
  $headers = smm_render_placeholders($headers, $vars);
  $body    = smm_render_placeholders($body, $vars);

  $payload = $body === null ? "" : json_encode($body, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);

  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

  // Reliability: force HTTP/1.1 (fixes some shared-host HTTP/2 weirdness)
  curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_setopt($ch, CURLOPT_TIMEOUT, 120);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);

  if ($payload !== "") curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

  $hdrs = [];
  if (is_array($headers)) {
    foreach ($headers as $k => $v) {
      if (is_int($k)) $hdrs[] = $v;
      else $hdrs[] = "{$k}: {$v}";
    }
  }
  if ($payload !== "" && !preg_grep('/content-type/i', $hdrs)) $hdrs[] = "Content-Type: application/json";
  if ($hdrs) curl_setopt($ch, CURLOPT_HTTPHEADER, $hdrs);

  $out  = curl_exec($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  $err  = curl_error($ch);
  curl_close($ch);

  if ($out === false) throw new Exception("HTTP tool {$tool['key']} curl error: {$err}");
  if ($code >= 400) throw new Exception("HTTP tool {$tool['key']} failed HTTP {$code}: ".substr((string)$out, 0, 500));

  $json = json_decode((string)$out, true);
  return ["raw" => $out, "json" => (is_array($json) ? $json : null)];
}


/**
 * OpenAI video generation (Sora) is async:
 * 1) POST /v1/videos -> {id,status}
 * 2) Poll GET /v1/videos/{id} until status==completed
 * 3) GET /v1/videos/{id}/content to download mp4
 * Returns local filename saved into /public/media and a post-safe relative URL.
 */
function smm_openai_video_generate(array $tool, string $prompt): ?array {
  $cfg = smm_cfg($tool);
  $api_key = (string)($cfg["api_key"] ?? "");
  if ($api_key === "") throw new Exception("Tool {$tool['key']} missing api_key in config_json");

  $baseUrl = (string)($cfg["base_url"] ?? "https://api.openai.com");
  $createUrl = rtrim($baseUrl, "/") . "/v1/videos";
  $model = (string)($cfg["model"] ?? "sora-2");
  $seconds = (string)($cfg["seconds"] ?? "4"); // allowed: 4,8,12
  $size = (string)($cfg["size"] ?? "720x1280"); // allowed sizes per docs

  // Create job (multipart form)
  $ch = curl_init($createUrl);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
  curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_setopt($ch, CURLOPT_TIMEOUT, 120);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
  curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer {$api_key}"]);
  curl_setopt($ch, CURLOPT_POSTFIELDS, [
    "prompt" => $prompt,
    "model" => $model,
    "seconds" => $seconds,
    "size" => $size,
  ]);

  $out = curl_exec($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  $err = curl_error($ch);
  curl_close($ch);

  if ($out === false) throw new Exception("openai_video curl error: {$err}");
  if ($code >= 400) throw new Exception("openai_video create failed HTTP {$code}: " . substr((string)$out, 0, 800));

  $j = json_decode((string)$out, true);
  if (!is_array($j) || empty($j["id"])) throw new Exception("openai_video create returned invalid response");
  $id = (string)$j["id"];

  // Poll status
  $statusUrl = rtrim($baseUrl, "/") . "/v1/videos/" . rawurlencode($id);
  $deadline = time() + intval($cfg["poll_timeout_sec"] ?? 180);
  $sleep = 2;

  while (time() < $deadline) {
    $ch = curl_init($statusUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 60);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer {$api_key}"]);
    $sout = curl_exec($ch);
    $scode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $serr = curl_error($ch);
    curl_close($ch);

    if ($sout === false) throw new Exception("openai_video status curl error: {$serr}");
    if ($scode >= 400) throw new Exception("openai_video status failed HTTP {$scode}: " . substr((string)$sout, 0, 800));

    $sj = json_decode((string)$sout, true);
    $st = (string)($sj["status"] ?? "");
    if ($st === "completed") break;
    if ($st === "failed" || $st === "canceled") throw new Exception("openai_video job {$id} ended with status: {$st}");

    sleep($sleep);
    if ($sleep < 8) $sleep++;
  }

  if (time() >= $deadline) throw new Exception("openai_video timed out waiting for completion");

  // Download content
  $contentUrl = rtrim($baseUrl, "/") . "/v1/videos/" . rawurlencode($id) . "/content";
  $ch = curl_init($contentUrl);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
  curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_setopt($ch, CURLOPT_TIMEOUT, 180);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
  curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer {$api_key}"]);
  $bin = curl_exec($ch);
  $ccode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  $cerr = curl_error($ch);
  $ctype = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
  curl_close($ch);

  if ($bin === false) throw new Exception("openai_video content curl error: {$cerr}");
  if ($ccode >= 400) throw new Exception("openai_video content failed HTTP {$ccode}: " . substr((string)$bin, 0, 300));

  // Save locally
  $fn = "vid_" . preg_replace('/[^a-zA-Z0-9_\-]/', '_', $id) . ".mp4";
  $p = smm_base_dir() . "/public/media/" . $fn;
  @mkdir(dirname($p), 0755, true);
  file_put_contents($p, $bin);

  return ["video_file" => $fn, "video_url" => "media/" . $fn];
}

/**
 * OpenAI writer tool: must return a JSON object with keys:
 * title, html, image_prompt, video_prompt
 */
function smm_openai_write(PDO $pdo, string $topic): array {
  $tool = smm_get_tool($pdo, "openai_writer");
  if (!$tool) throw new Exception("Missing tool: openai_writer");

  $cfg = smm_cfg($tool);
  $api_key = (string)($cfg["api_key"] ?? "");
  if ($api_key === "") throw new Exception("Tool openai_writer missing api_key in config_json");

  $url   = (string)($cfg["url"] ?? "https://api.openai.com/v1/responses");
  $model = (string)($cfg["model"] ?? "gpt-4.1-mini");
  $system = (string)($cfg["system"] ?? "You are a helpful writing assistant. Output clean HTML (no markdown).");
  $temp  = $cfg["temperature"] ?? 0.7;

  $payload = [
    "model" => $model,
    "temperature" => $temp,
    "input" => [
      ["role" => "system", "content" => $system],
      ["role" => "user", "content" =>
        "Write a publish-ready blog article about: {$topic}\n" .
        "Return ONLY JSON with keys: title, html, image_prompt, video_prompt.\n" .
        "html must be valid HTML (no markdown)."
      ],
    ],
    "text" => ["format" => ["type" => "json_object"]],
  ];

  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
  curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_setopt($ch, CURLOPT_TIMEOUT, 120);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer {$api_key}",
    "Content-Type: application/json",
  ]);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE));

  $out  = curl_exec($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  $err  = curl_error($ch);
  curl_close($ch);

  if ($out === false) throw new Exception("openai_writer curl error: {$err}");
  if ($code >= 400) throw new Exception("openai_writer failed HTTP {$code}: " . substr((string)$out, 0, 800));

  $j = json_decode((string)$out, true);
  if (!is_array($j)) throw new Exception("openai_writer returned non-JSON response");

  $text = $j["output"][0]["content"][0]["text"] ?? null;
  if (!$text) throw new Exception("openai_writer returned no text");

  $obj = json_decode((string)$text, true);
  if (!is_array($obj) || empty($obj["title"]) || empty($obj["html"])) {
    throw new Exception("openai_writer returned invalid JSON payload");
  }

  $obj["image_prompt"] = (string)($obj["image_prompt"] ?? "");
  $obj["video_prompt"] = (string)($obj["video_prompt"] ?? "");
  return $obj;
}

/**
 * TagX: requires your tagx tool config to include:
 * base_url, endpoint, and optionally x_key
 */
function smm_tagx(PDO $pdo, string $text, int $max = 10): array {
  $tool = smm_get_tool($pdo, "tagx_main");
  if (!$tool) throw new Exception("Missing tool: tagx_main");

  $cfg = smm_cfg($tool);
  $base = rtrim((string)($cfg["base_url"] ?? ""), "/");
  $ep   = (string)($cfg["endpoint"] ?? "/api/tagx.php");
  $xkey = (string)($cfg["x_key"] ?? ""); // if your TagX requires a key header

  if ($base === "") throw new Exception("tagx_main missing base_url");
  $url = $base . $ep;

  $headers = ["Content-Type: application/json"];
  if ($xkey !== "") $headers[] = "X-TagX-Key: {$xkey}";

  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
  curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_setopt($ch, CURLOPT_TIMEOUT, 60);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
  curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["text" => $text, "max_tags" => $max], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE));

  $out  = curl_exec($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  $err  = curl_error($ch);
  curl_close($ch);

  if ($out === false) return [];         // TagX failure should not kill whole flow
  if ($code >= 400) return [];

  $j = json_decode((string)$out, true);
  $tags = $j["tags"] ?? [];
  if (!is_array($tags)) $tags = [];
  return $tags;
}

function smm_save_article(PDO $pdo, array $a): int {
  $now = smm_now_iso();
  $st = $pdo->prepare(
    "INSERT INTO articles (title,slug,body_html,tags_json,media_json,site_profile_id,status,created_at,updated_at)
     VALUES (?,?,?,?,?,?,?,?,?)"
  );
  $st->execute([
    $a["title"],
    $a["slug"] ?? "",
    $a["body_html"],
    $a["tags_json"] ?? "[]",
    $a["media_json"] ?? "{}",
    $a["site_profile_id"] ?? null,
    $a["status"] ?? "draft",
    $now,
    $now
  ]);
  return (int)$pdo->lastInsertId();
}

function smm_latest_article_id(PDO $pdo): int {
  $row = $pdo->query("SELECT id FROM articles ORDER BY id DESC LIMIT 1")->fetch(PDO::FETCH_ASSOC);
  return $row ? (int)$row["id"] : 0;
}



function smm_copy_media_into_post_dir(string $postDir, array $media): array {
  $base = smm_base_dir();
  $publicMedia = rtrim($base, "/") . "/public/media";
  $out = $media;

  $mediaDir = rtrim($postDir, "/") . "/media";
  if (!is_dir($mediaDir)) @mkdir($mediaDir, 0755, true);

  foreach (["image_file"=>"image_url", "video_file"=>"video_url"] as $fileKey => $urlKey) {
    $fn = trim((string)($media[$fileKey] ?? ""));
    if ($fn === "") continue;

    $srcPath = rtrim($publicMedia, "/") . "/" . basename($fn);
    $dstPath = rtrim($mediaDir, "/") . "/" . basename($fn);

    if (is_file($srcPath)) {
      @copy($srcPath, $dstPath);
      $out[$urlKey] = "media/" . basename($fn);
    }
  }
  return $out;
}

function smm_publish_static(PDO $pdo, int $articleId): string {
  $st = $pdo->prepare("SELECT * FROM articles WHERE id=?");
  $st->execute([$articleId]);
  $a = $st->fetch(PDO::FETCH_ASSOC);
  if (!$a) throw new Exception("Article not found");

  $siteId = (int)($a["site_profile_id"] ?? 0);
  if ($siteId <= 0) throw new Exception("Article has no site selected");

  $st = $pdo->prepare("SELECT * FROM sites WHERE id=?");
  $st->execute([$siteId]);
  $site = $st->fetch(PDO::FETCH_ASSOC);
  if (!$site) throw new Exception("Site not found");

  $slug = trim((string)($a["slug"] ?: strtolower(preg_replace('/[^a-z0-9]+/i','-', (string)$a["title"]))));
  $slug = trim($slug, '-');
  if ($slug === "") $slug = "post-" . $articleId;

  $dir = rtrim((string)$site["publish_dir"], "/") . "/" . $slug;
  if (!is_dir($dir)) {
    if (!@mkdir($dir, 0755, true)) throw new Exception("Failed to create folder: ".$dir);
  }
  if (!is_writable($dir)) throw new Exception("Folder not writable: ".$dir);

  $tags = json_decode((string)($a["tags_json"] ?? "[]"), true);
  if (!is_array($tags)) $tags = [];

  $media = json_decode((string)($a["media_json"] ?? "{}"), true);
  if (!is_array($media)) $media = [];

  // Ensure local media files are copied into this post folder
  $media = smm_copy_media_into_post_dir($dir, $media);

  $img = !empty($media["image_url"])
    ? "<img src=\"".htmlspecialchars((string)$media["image_url"])."\" style=\"max-width:100%;height:auto;border-radius:12px;\" />"
    : "";

  $vid = !empty($media["video_url"])
    ? "<video controls style=\"max-width:100%;border-radius:12px;\"><source src=\"".htmlspecialchars((string)$media["video_url"])."\" type=\"video/mp4\"></video>"
    : "";

  $tagLine = $tags ? implode(" ", $tags) : "";

  $html =
"<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">".
"<title>".htmlspecialchars((string)$a["title"])."</title>".
"<style>body{font-family:system-ui;margin:0;background:#070b14;color:#e8eefc} .wrap{max-width:900px;margin:0 auto;padding:24px} .card{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:18px;padding:18px}</style>".
"</head><body><div class=\"wrap\"><div class=\"card\">".
"<h1>".htmlspecialchars((string)$a["title"])."</h1>".
$img.$vid.
"<article>".$a["body_html"]."</article>".
($tagLine ? "<hr><div style=\"margin-top:12px\"><b>Tags:</b> ".htmlspecialchars($tagLine)."</div>" : "").
"<div style=\"opacity:.7;margin-top:14px\">Published via SocialMediaManager</div>".
"</div></div></body></html>";

  file_put_contents($dir . "/index.html", $html);

  $url = rtrim((string)$site["base_url"], "/") . "/" . $slug . "/";

  $now = smm_now_iso();
  $st = $pdo->prepare("UPDATE articles SET status='published', slug=?, updated_at=? WHERE id=?");
  $st->execute([$slug, $now, $articleId]);

  return $url;
}

function smm_default_site_id(PDO $pdo): int {
  $s = $pdo->query("SELECT id FROM sites ORDER BY updated_at DESC LIMIT 1")->fetch(PDO::FETCH_ASSOC);
  return ($s && !empty($s["id"])) ? (int)$s["id"] : 0;
}

/**
 * Main request handler
 */
try {
  $pdo = smm_db();
  $b = smm_read_json_body();
  $msg = trim((string)($b["message"] ?? ""));
  if ($msg === "") jacai_out(["ok"=>false,"error"=>"message required"], 400);

  // Commands
  $isPublishLatest = (bool)preg_match('/\bpublish\s+latest\b/i', $msg);
  $isTagxLatest    = (bool)preg_match('/\btagx\s+latest\b/i', $msg);

  if ($isPublishLatest) {
    $id = smm_latest_article_id($pdo);
    if ($id <= 0) jacai_out(["ok"=>false,"error"=>"No articles found"], 400);
    $url = smm_publish_static($pdo, $id);
    jacai_out(["ok"=>true, "result"=>["reply"=>"Published latest ✅\nDraft #{$id}\nURL: {$url}", "article_id"=>$id, "url"=>$url]]);
  }

  if ($isTagxLatest) {
    $id = smm_latest_article_id($pdo);
    if ($id <= 0) jacai_out(["ok"=>false,"error"=>"No articles found"], 400);
    $st = $pdo->prepare("SELECT * FROM articles WHERE id=?");
    $st->execute([$id]);
    $a = $st->fetch(PDO::FETCH_ASSOC);
    if (!$a) jacai_out(["ok"=>false,"error"=>"Article not found"], 400);

    $tags = smm_tagx($pdo, strip_tags((string)$a["body_html"]), 10);
    $tags = array_values(array_filter(array_map(function($t){
      $t = trim((string)$t);
      if ($t === "") return "";
      if ($t[0] !== "#") $t = "#".preg_replace('/\s+/', '', $t);
      return $t;
    }, $tags)));
    $tags = array_slice($tags, 0, 12);

    $now = smm_now_iso();
    $st = $pdo->prepare("UPDATE articles SET tags_json=?, updated_at=? WHERE id=?");
    $st->execute([json_encode($tags, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE), $now, $id]);

    jacai_out(["ok"=>true, "result"=>["reply"=>"TagX applied to latest ✅\nDraft #{$id}\nTags: ".implode(" ", $tags), "article_id"=>$id]]);
  }

  // Intent flags in freeform prompt
  $wantPublish = (bool)preg_match('/\bpublish\b/i', $msg);
  $wantTagx    = (bool)preg_match('/\btagx\b|\btags?\b|\bhashtags?\b/i', $msg);
  $wantImage   = (bool)preg_match('/\bimage\b|\bpicture\b|\bthumbnail\b/i', $msg);
  $wantVideo   = (bool)preg_match('/\bvideo\b|\bsora\b/i', $msg);

  // Topic: strip command words (light)
  $topic = preg_replace('/\b(write|create|make|publish|latest|post|article)\b/i', '', $msg);
  $topic = trim($topic);
  if ($topic === "") $topic = $msg;

  // 1) Write
  $w = smm_openai_write($pdo, $topic);
  $title = (string)$w["title"];
  $body_html = (string)$w["html"];

  $media = [];
  $steps = ["write"];

  // 2) TagX
  $tags = [];
  if ($wantTagx) {
    $tags = smm_tagx($pdo, strip_tags($body_html), 10);
    $tags = array_values(array_filter(array_map(function($t){
      $t = trim((string)$t);
      if ($t === "") return "";
      if ($t[0] !== "#") $t = "#".preg_replace('/\s+/', '', $t);
      return $t;
    }, $tags)));
    $tags = array_slice($tags, 0, 12);
    $steps[] = "tagx";
  }

  // 3) Image
  if ($wantImage) {
    $t = smm_get_tool($pdo, "openai_image_gen");
    if ($t) {
      $imgPrompt = (string)($w["image_prompt"] ?: ("Create a clean blog hero image for: ".$title));
      $r = smm_http_call($t, ["prompt"=>$imgPrompt]);

      $b64 = $r["json"]["data"][0]["b64_json"] ?? null;
      $url = $r["json"]["data"][0]["url"] ?? null;

      if ($url) {
        $media["image_url"] = $url;
      } elseif ($b64) {
        $bin = base64_decode((string)$b64);
        if ($bin !== false) {
          $fn = "img_" . time() . "_" . bin2hex(random_bytes(2)) . ".png";
          $p = smm_base_dir() . "/public/media/" . $fn;
          @mkdir(dirname($p), 0755, true);
          file_put_contents($p, $bin);
          // relative path inside published HTML (works if media folder is reachable)
          $media["image_file"] = $fn;
          $media["image_url"] = "media/" . $fn;
        }
      }
      $steps[] = "image";
    }
  }

  // 4) Video (OpenAI Videos API is async; download MP4 locally for reliable embedding)
if ($wantVideo) {
  $t = smm_get_tool($pdo, "openai_video");
  if ($t) {
    $vidPrompt = (string)($w["video_prompt"] ?: ("Short cinematic clip for: " . $title));
    $vres = smm_openai_video_generate($t, $vidPrompt);
    if ($vres && !empty($vres["video_file"])) {
      $media["video_file"] = (string)$vres["video_file"];
      $media["video_url"]  = (string)$vres["video_url"];
    }
    $steps[] = "video";
  }
}

// 5) Save article draft
  $siteId = smm_default_site_id($pdo);
  $articleId = smm_save_article($pdo, [
    "title" => $title,
    "body_html" => $body_html,
    "tags_json" => json_encode($tags, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE),
    "media_json" => json_encode($media, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE),
    "site_profile_id" => ($siteId > 0 ? $siteId : null),
    "status" => "draft",
  ]);

  // 6) Publish if requested
  if ($wantPublish) {
    $url = smm_publish_static($pdo, $articleId);
    $steps[] = "publish";
    jacai_out([
      "ok" => true,
      "result" => [
        "reply" => "Done ✅\nDraft #{$articleId}\nSteps: ".implode(", ", $steps)."\nURL: {$url}",
        "article_id" => $articleId,
        "url" => $url,
      ]
    ]);
  }

  jacai_out([
    "ok" => true,
    "result" => [
      "reply" => "Done ✅\nDraft #{$articleId}\nSteps: ".implode(", ", $steps)."\nNext: say 'publish latest' or include 'publish' in your request.",
      "article_id" => $articleId
    ]
  ]);

} catch (Throwable $e) {
  smm_log("JacAI error: ".$e->getMessage(), "ERROR");
  jacai_out(["ok"=>false,"error"=>$e->getMessage()], 500);
}
