#!/bin/sh
# SPDX-License-Identifier: MIT
# Hexo CMS RPCD backend
# Copyright (C) 2025 CyberMind.fr

. /lib/functions.sh
. /usr/share/libubox/jshn.sh

CONFIG="hexojs"
HEXOCTL="/usr/sbin/hexoctl"
DATA_PATH="/srv/hexojs"

# Helper functions
uci_get() { uci -q get ${CONFIG}.$1; }
uci_set() { uci set ${CONFIG}.$1="$2" && uci commit ${CONFIG}; }

is_running() {
	pgrep -f "lxc.*hexojs" >/dev/null 2>&1
}

get_site_path() {
	local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
	echo "$data_path/site"
}

# ============================================
# Status Methods
# ============================================

get_status() {
	local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
	local site_path="$data_path/site"
	local enabled=$(uci_get main.enabled) || enabled="0"
	local http_port=$(uci_get main.http_port) || http_port="4000"
	local active_site=$(uci_get main.active_site) || active_site="default"

	json_init

	json_add_boolean "enabled" "$enabled"
	json_add_boolean "running" "$(is_running && echo 1 || echo 0)"
	json_add_int "http_port" "$http_port"
	json_add_string "active_site" "$active_site"
	json_add_string "data_path" "$data_path"

	# Site info
	if [ -d "$site_path" ]; then
		json_add_boolean "site_exists" 1

		local post_count=0
		local draft_count=0
		local page_count=0

		# Recursive count for posts (handles subdirectory categories)
		[ -d "$site_path/source/_posts" ] && post_count=$(find "$site_path/source/_posts" -type f -name "*.md" ! -name "index.md" 2>/dev/null | wc -l)
		[ -d "$site_path/source/_drafts" ] && draft_count=$(find "$site_path/source/_drafts" -type f -name "*.md" ! -name "index.md" 2>/dev/null | wc -l)

		json_add_int "post_count" "$post_count"
		json_add_int "draft_count" "$draft_count"

		# Site config
		local title=$(uci_get ${active_site}.title) || title="Blog"
		local author=$(uci_get ${active_site}.author) || author="Admin"
		local theme=$(uci_get ${active_site}.theme) || theme="cybermind"

		json_add_object "site"
		json_add_string "title" "$title"
		json_add_string "author" "$author"
		json_add_string "theme" "$theme"
		json_close_object
	else
		json_add_boolean "site_exists" 0
		json_add_int "post_count" 0
		json_add_int "draft_count" 0
	fi

	json_dump
}

get_site_stats() {
	local site_path=$(get_site_path)

	json_init

	if [ ! -d "$site_path" ]; then
		json_add_int "posts" 0
		json_add_int "drafts" 0
		json_add_int "pages" 0
		json_add_int "categories" 0
		json_add_int "tags" 0
		json_add_int "media" 0
		json_dump
		return
	fi

	local post_count=0
	local draft_count=0
	local page_count=0
	local media_count=0
	local tmp_posts="/tmp/hexojs_stats_$$"

	# Recursive counts (handles subdirectory categories)
	[ -d "$site_path/source/_posts" ] && post_count=$(find "$site_path/source/_posts" -type f -name "*.md" ! -name "index.md" 2>/dev/null | wc -l)
	[ -d "$site_path/source/_drafts" ] && draft_count=$(find "$site_path/source/_drafts" -type f -name "*.md" ! -name "index.md" 2>/dev/null | wc -l)
	[ -d "$site_path/source/images" ] && media_count=$(find "$site_path/source/images" -type f 2>/dev/null | wc -l)

	# Count unique categories and tags from posts (recursive)
	local categories=""
	local tags=""

	if [ -d "$site_path/source/_posts" ]; then
		find "$site_path/source/_posts" -type f -name "*.md" ! -name "index.md" 2>/dev/null > "$tmp_posts"
		while read f; do
			[ -f "$f" ] || continue
			local cat=$(grep -m1 "^categories:" "$f" 2>/dev/null | sed 's/^categories:[[:space:]]*//' | tr -d '[]' | tr ',' '\n')
			local tag=$(grep -m1 "^tags:" "$f" 2>/dev/null | sed 's/^tags:[[:space:]]*//' | tr -d '[]' | tr ',' '\n')
			# Also use directory name as category if no category in front matter
			if [ -z "$cat" ]; then
				local rel_path="${f#$site_path/source/_posts/}"
				local cat_dir=$(dirname "$rel_path")
				[ "$cat_dir" != "." ] && cat="$cat_dir"
			fi
			categories="$categories
$cat"
			tags="$tags
$tag"
		done < "$tmp_posts"
		rm -f "$tmp_posts"
	fi

	local cat_count=$(echo "$categories" | grep -v '^$' | sort -u | wc -l)
	local tag_count=$(echo "$tags" | grep -v '^$' | sort -u | wc -l)

	json_add_int "posts" "$post_count"
	json_add_int "drafts" "$draft_count"
	json_add_int "pages" "$page_count"
	json_add_int "categories" "$cat_count"
	json_add_int "tags" "$tag_count"
	json_add_int "media" "$media_count"

	json_dump
}

# ============================================
# Post Methods
# ============================================

list_posts() {
	local site_path=$(get_site_path)
	local posts_dir="$site_path/source/_posts"
	local tmp_posts="/tmp/hexojs_posts_$$"

	json_init
	json_add_array "posts"

	if [ -d "$posts_dir" ]; then
		# Recursively find all markdown files (handles subdirectory categories)
		find "$posts_dir" -type f -name "*.md" ! -name "index.md" 2>/dev/null > "$tmp_posts"

		while read f; do
			[ -f "$f" ] || continue

			local filename=$(basename "$f")
			local slug="${filename%.md}"

			# Get relative path from _posts for category detection
			local rel_path="${f#$posts_dir/}"
			local category_dir=$(dirname "$rel_path")
			[ "$category_dir" = "." ] && category_dir=""

			# Parse front matter (sanitize for JSON)
			local title=$(grep -m1 "^title:" "$f" 2>/dev/null | sed 's/^title:[[:space:]]*//' | tr -d '"' | tr -d "'" | tr -d '\r')
			local date=$(grep -m1 "^date:" "$f" 2>/dev/null | sed 's/^date:[[:space:]]*//' | tr -d '\r')
			local categories=$(grep -m1 "^categories:" "$f" 2>/dev/null | sed 's/^categories:[[:space:]]*//' | tr -d '[]' | tr -d '\r')
			local tags=$(grep -m1 "^tags:" "$f" 2>/dev/null | sed 's/^tags:[[:space:]]*//' | tr -d '[]' | tr -d '\r')
			local excerpt=$(grep -m1 "^excerpt:" "$f" 2>/dev/null | sed 's/^excerpt:[[:space:]]*//' | tr -d '"' | tr -d '\r' | cut -c1-200)

			# Use directory as category if not specified in front matter
			[ -z "$categories" ] && [ -n "$category_dir" ] && categories="$category_dir"

			# Truncate title if too long
			title=$(echo "$title" | cut -c1-150)

			json_add_object
			json_add_string "slug" "$slug"
			json_add_string "title" "${title:-$slug}"
			json_add_string "date" "$date"
			json_add_string "categories" "$categories"
			json_add_string "tags" "$tags"
			json_add_string "excerpt" "$excerpt"
			json_add_string "path" "$f"
			json_add_string "category_dir" "$category_dir"
			json_close_object
		done < "$tmp_posts"

		rm -f "$tmp_posts"
	fi

	json_close_array
	json_dump
}

get_post() {
	read input
	json_load "$input"
	json_get_var slug slug
	json_get_var path path ""

	json_init

	if [ -z "$slug" ] && [ -z "$path" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Slug or path required"
		json_dump
		return
	fi

	local site_path=$(get_site_path)
	local post_file=""

	# If path provided, use it directly
	if [ -n "$path" ] && [ -f "$path" ]; then
		post_file="$path"
	else
		# Search for post file (first in root, then recursively)
		post_file="$site_path/source/_posts/${slug}.md"
		if [ ! -f "$post_file" ]; then
			# Search recursively in subdirectories
			post_file=$(find "$site_path/source/_posts" -type f -name "${slug}.md" 2>/dev/null | head -1)
		fi
	fi

	if [ -z "$post_file" ] || [ ! -f "$post_file" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Post not found: $slug"
		json_dump
		return
	fi

	# Parse front matter and content
	local title=$(grep -m1 "^title:" "$post_file" | sed 's/^title:[[:space:]]*//' | tr -d '"' | tr -d "'")
	local date=$(grep -m1 "^date:" "$post_file" | sed 's/^date:[[:space:]]*//')
	local categories=$(grep -m1 "^categories:" "$post_file" | sed 's/^categories:[[:space:]]*//' | tr -d '[]')
	local tags=$(grep -m1 "^tags:" "$post_file" | sed 's/^tags:[[:space:]]*//' | tr -d '[]')
	local cover=$(grep -m1 "^cover:" "$post_file" | sed 's/^cover:[[:space:]]*//')
	local excerpt=$(grep -m1 "^excerpt:" "$post_file" | sed 's/^excerpt:[[:space:]]*//' | tr -d '"')

	# Get content after front matter
	local content=$(awk '/^---$/,/^---$/{next} {print}' "$post_file" | tail -n +2)

	json_add_boolean "success" 1
	json_add_string "slug" "$slug"
	json_add_string "title" "$title"
	json_add_string "date" "$date"
	json_add_string "categories" "$categories"
	json_add_string "tags" "$tags"
	json_add_string "cover" "$cover"
	json_add_string "excerpt" "$excerpt"
	json_add_string "content" "$content"
	json_add_string "path" "$post_file"

	json_dump
}

create_post() {
	read input
	json_load "$input"
	json_get_var title title
	json_get_var content content
	json_get_var categories categories
	json_get_var tags tags
	json_get_var excerpt excerpt

	json_init

	if [ -z "$title" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Title required"
		json_dump
		return
	fi

	# Generate slug from title
	local slug=$(echo "$title" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-')
	local date=$(date "+%Y-%m-%d %H:%M:%S")

	local site_path=$(get_site_path)
	local posts_dir="$site_path/source/_posts"
	local post_file="$posts_dir/${slug}.md"

	[ -d "$posts_dir" ] || mkdir -p "$posts_dir"

	# Check if file exists
	if [ -f "$post_file" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Post with this slug already exists"
		json_dump
		return
	fi

	# Create post file
	cat > "$post_file" << EOF
---
title: $title
date: $date
categories: [$categories]
tags: [$tags]
excerpt: $excerpt
---

$content
EOF

	json_add_boolean "success" 1
	json_add_string "slug" "$slug"
	json_add_string "path" "$post_file"
	json_add_string "message" "Post created"

	json_dump
}

update_post() {
	read input
	json_load "$input"
	json_get_var slug slug
	json_get_var title title
	json_get_var content content
	json_get_var categories categories
	json_get_var tags tags
	json_get_var excerpt excerpt
	json_get_var cover cover

	json_init

	if [ -z "$slug" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Slug required"
		json_dump
		return
	fi

	local site_path=$(get_site_path)
	local post_file="$site_path/source/_posts/${slug}.md"

	if [ ! -f "$post_file" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Post not found"
		json_dump
		return
	fi

	# Get original date
	local date=$(grep -m1 "^date:" "$post_file" | sed 's/^date:[[:space:]]*//')

	# Rewrite post file
	cat > "$post_file" << EOF
---
title: $title
date: $date
categories: [$categories]
tags: [$tags]
cover: $cover
excerpt: $excerpt
---

$content
EOF

	json_add_boolean "success" 1
	json_add_string "message" "Post updated"

	json_dump
}

delete_post() {
	read input
	json_load "$input"
	json_get_var slug slug

	json_init

	if [ -z "$slug" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Slug required"
		json_dump
		return
	fi

	local site_path=$(get_site_path)
	local post_file="$site_path/source/_posts/${slug}.md"

	if [ ! -f "$post_file" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Post not found"
		json_dump
		return
	fi

	rm -f "$post_file"

	json_add_boolean "success" 1
	json_add_string "message" "Post deleted"

	json_dump
}

publish_post() {
	read input
	json_load "$input"
	json_get_var slug slug

	json_init

	if [ -z "$slug" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Slug required"
		json_dump
		return
	fi

	local site_path=$(get_site_path)
	local draft_file="$site_path/source/_drafts/${slug}.md"
	local posts_dir="$site_path/source/_posts"
	local post_file="$posts_dir/${slug}.md"

	if [ ! -f "$draft_file" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Draft not found"
		json_dump
		return
	fi

	[ -d "$posts_dir" ] || mkdir -p "$posts_dir"

	# Add date if not present
	if ! grep -q "^date:" "$draft_file"; then
		local date=$(date "+%Y-%m-%d %H:%M:%S")
		sed -i "/^title:/a date: $date" "$draft_file"
	fi

	mv "$draft_file" "$post_file"

	json_add_boolean "success" 1
	json_add_string "message" "Draft published"
	json_add_string "path" "$post_file"

	json_dump
}

list_drafts() {
	local site_path=$(get_site_path)
	local drafts_dir="$site_path/source/_drafts"

	json_init
	json_add_array "drafts"

	if [ -d "$drafts_dir" ]; then
		for f in "$drafts_dir"/*.md; do
			[ -f "$f" ] || continue

			local filename=$(basename "$f")
			local slug="${filename%.md}"
			local title=$(grep -m1 "^title:" "$f" | sed 's/^title:[[:space:]]*//' | tr -d '"' | tr -d "'")

			json_add_object
			json_add_string "slug" "$slug"
			json_add_string "title" "${title:-$slug}"
			json_add_string "path" "$f"
			json_close_object
		done
	fi

	json_close_array
	json_dump
}

search_posts() {
	read input
	json_load "$input"
	json_get_var query query
	json_get_var category category
	json_get_var tag tag

	local site_path=$(get_site_path)
	local posts_dir="$site_path/source/_posts"
	local tmp_posts="/tmp/hexojs_search_$$"

	json_init
	json_add_array "posts"

	if [ -d "$posts_dir" ]; then
		# Recursively find all posts
		find "$posts_dir" -type f -name "*.md" ! -name "index.md" 2>/dev/null > "$tmp_posts"

		while read f; do
			[ -f "$f" ] || continue

			local match=1

			# Filter by query
			if [ -n "$query" ]; then
				grep -qi "$query" "$f" || match=0
			fi

			# Get category from front matter or directory
			local file_cat=$(grep -m1 "^categories:" "$f" | sed 's/^categories:[[:space:]]*//' | tr -d '[]')
			if [ -z "$file_cat" ]; then
				local rel_path="${f#$posts_dir/}"
				local cat_dir=$(dirname "$rel_path")
				[ "$cat_dir" != "." ] && file_cat="$cat_dir"
			fi

			# Filter by category
			if [ -n "$category" ] && [ "$match" = "1" ]; then
				echo "$file_cat" | grep -qi "$category" || match=0
			fi

			# Filter by tag
			if [ -n "$tag" ] && [ "$match" = "1" ]; then
				grep -qi "tags:.*$tag" "$f" || match=0
			fi

			if [ "$match" = "1" ]; then
				local filename=$(basename "$f")
				local slug="${filename%.md}"
				local title=$(grep -m1 "^title:" "$f" | sed 's/^title:[[:space:]]*//' | tr -d '"' | tr -d "'")
				local date=$(grep -m1 "^date:" "$f" | sed 's/^date:[[:space:]]*//')

				json_add_object
				json_add_string "slug" "$slug"
				json_add_string "title" "${title:-$slug}"
				json_add_string "date" "$date"
				json_add_string "path" "$f"
				json_add_string "categories" "$file_cat"
				json_close_object
			fi
		done < "$tmp_posts"

		rm -f "$tmp_posts"
	fi

	json_close_array
	json_dump
}

# ============================================
# Taxonomy Methods
# ============================================

list_categories() {
	local site_path=$(get_site_path)
	local posts_dir="$site_path/source/_posts"
	local tmp_posts="/tmp/hexojs_cats_$$"
	local tmp_cats="/tmp/hexojs_cats_list_$$"
	local tmp_counts="/tmp/hexojs_cats_counts_$$"

	json_init
	json_add_array "categories"

	if [ -d "$posts_dir" ]; then
		# Recursively find all posts
		find "$posts_dir" -type f -name "*.md" ! -name "index.md" 2>/dev/null > "$tmp_posts"
		: > "$tmp_cats"

		while read f; do
			[ -f "$f" ] || continue
			local cat=$(grep -m1 "^categories:" "$f" | sed 's/^categories:[[:space:]]*//' | tr -d '[]' | tr ',' '\n')
			# Use directory as category if not in front matter
			if [ -z "$cat" ]; then
				local rel_path="${f#$posts_dir/}"
				local cat_dir=$(dirname "$rel_path")
				[ "$cat_dir" != "." ] && cat="$cat_dir"
			fi
			echo "$cat" >> "$tmp_cats"
		done < "$tmp_posts"

		# Count unique categories and save to temp file
		grep -v '^$' "$tmp_cats" 2>/dev/null | sort | uniq -c > "$tmp_counts"

		# Read counts and add to JSON (avoid piped while loop to preserve JSON context)
		while read count name; do
			name=$(echo "$name" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
			[ -n "$name" ] || continue
			json_add_object
			json_add_string "name" "$name"
			json_add_int "count" "$count"
			json_close_object
		done < "$tmp_counts"

		rm -f "$tmp_posts" "$tmp_cats" "$tmp_counts"
	fi

	json_close_array
	json_dump
}

list_tags() {
	local site_path=$(get_site_path)
	local posts_dir="$site_path/source/_posts"
	local tmp_posts="/tmp/hexojs_tags_$$"
	local tmp_tags="/tmp/hexojs_tags_list_$$"
	local tmp_counts="/tmp/hexojs_tags_counts_$$"

	json_init
	json_add_array "tags"

	if [ -d "$posts_dir" ]; then
		# Recursively find all posts
		find "$posts_dir" -type f -name "*.md" ! -name "index.md" 2>/dev/null > "$tmp_posts"
		: > "$tmp_tags"

		while read f; do
			[ -f "$f" ] || continue
			local tag=$(grep -m1 "^tags:" "$f" | sed 's/^tags:[[:space:]]*//' | tr -d '[]' | tr ',' '\n')
			echo "$tag" >> "$tmp_tags"
		done < "$tmp_posts"

		# Count unique tags and save to temp file
		grep -v '^$' "$tmp_tags" 2>/dev/null | sort | uniq -c > "$tmp_counts"

		# Read counts and add to JSON (avoid piped while loop to preserve JSON context)
		while read count name; do
			name=$(echo "$name" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
			[ -n "$name" ] || continue
			json_add_object
			json_add_string "name" "$name"
			json_add_int "count" "$count"
			json_close_object
		done < "$tmp_counts"

		rm -f "$tmp_posts" "$tmp_tags" "$tmp_counts"
	fi

	json_close_array
	json_dump
}

# ============================================
# Media Methods
# ============================================

list_media() {
	local site_path=$(get_site_path)
	local media_dir="$site_path/source/images"
	local tmp_media="/tmp/hexojs_media_$$"

	json_init
	json_add_array "media"

	if [ -d "$media_dir" ]; then
		# Save find results to temp file to avoid piped while loop
		find "$media_dir" -type f 2>/dev/null > "$tmp_media"

		while read f; do
			[ -f "$f" ] || continue
			local filename=$(basename "$f")
			local size=$(stat -c %s "$f" 2>/dev/null || echo 0)
			local mtime=$(stat -c %Y "$f" 2>/dev/null || echo 0)
			local relpath="${f#$site_path/source}"

			json_add_object
			json_add_string "name" "$filename"
			json_add_string "path" "$relpath"
			json_add_string "full_path" "$f"
			json_add_int "size" "$size"
			json_add_int "mtime" "$mtime"
			json_close_object
		done < "$tmp_media"

		rm -f "$tmp_media"
	fi

	json_close_array
	json_dump
}

delete_media() {
	read input
	json_load "$input"
	json_get_var path path

	json_init

	if [ -z "$path" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Path required"
		json_dump
		return
	fi

	local site_path=$(get_site_path)
	local full_path="$site_path/source$path"

	if [ ! -f "$full_path" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "File not found"
		json_dump
		return
	fi

	rm -f "$full_path"

	json_add_boolean "success" 1
	json_add_string "message" "Media deleted"

	json_dump
}

# ============================================
# Apps (Portfolio) Methods
# ============================================

list_apps() {
	local site_path=$(get_site_path)
	local apps_dir="$site_path/source/apps"

	json_init
	json_add_array "apps"

	if [ -d "$apps_dir" ]; then
		for f in "$apps_dir"/*.md; do
			[ -f "$f" ] || continue

			local filename=$(basename "$f")
			local slug="${filename%.md}"
			local title=$(grep -m1 "^title:" "$f" | sed 's/^title:[[:space:]]*//' | tr -d '"' | tr -d "'")
			local icon=$(grep -m1 "^icon:" "$f" | sed 's/^icon:[[:space:]]*//')
			local description=$(grep -m1 "^description:" "$f" | sed 's/^description:[[:space:]]*//' | tr -d '"')
			local url=$(grep -m1 "^url:" "$f" | sed 's/^url:[[:space:]]*//')
			local category=$(grep -m1 "^category:" "$f" | sed 's/^category:[[:space:]]*//')
			local featured=$(grep -m1 "^featured:" "$f" | sed 's/^featured:[[:space:]]*//')

			json_add_object
			json_add_string "slug" "$slug"
			json_add_string "title" "${title:-$slug}"
			json_add_string "icon" "$icon"
			json_add_string "description" "$description"
			json_add_string "url" "$url"
			json_add_string "category" "$category"
			json_add_boolean "featured" "$([ "$featured" = "true" ] && echo 1 || echo 0)"
			json_close_object
		done
	fi

	json_close_array
	json_dump
}

create_app() {
	read input
	json_load "$input"
	json_get_var title title
	json_get_var icon icon
	json_get_var description description
	json_get_var url url
	json_get_var category category
	json_get_var content content

	json_init

	if [ -z "$title" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Title required"
		json_dump
		return
	fi

	local slug=$(echo "$title" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-')
	local date=$(date "+%Y-%m-%d %H:%M:%S")

	local site_path=$(get_site_path)
	local apps_dir="$site_path/source/apps"
	local app_file="$apps_dir/${slug}.md"

	[ -d "$apps_dir" ] || mkdir -p "$apps_dir"

	if [ -f "$app_file" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "App with this slug already exists"
		json_dump
		return
	fi

	cat > "$app_file" << EOF
---
title: $title
date: $date
layout: app
icon: $icon
description: $description
url: $url
category: ${category:-tools}
featured: false
status: active
---

$content
EOF

	json_add_boolean "success" 1
	json_add_string "slug" "$slug"
	json_add_string "message" "App created"

	json_dump
}

# ============================================
# Build & Deploy Methods
# ============================================

do_generate() {
	json_init

	if ! is_running; then
		json_add_boolean "success" 0
		json_add_string "error" "Container not running"
		json_dump
		return
	fi

	local output=$("$HEXOCTL" exec sh -c "cd /opt/hexojs/site && hexo generate" 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Site generated successfully"
	else
		json_add_boolean "success" 0
		json_add_string "error" "$output"
	fi

	json_dump
}

do_clean() {
	json_init

	if ! is_running; then
		json_add_boolean "success" 0
		json_add_string "error" "Container not running"
		json_dump
		return
	fi

	local output=$("$HEXOCTL" exec sh -c "cd /opt/hexojs/site && hexo clean" 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Site cleaned"
	else
		json_add_boolean "success" 0
		json_add_string "error" "$output"
	fi

	json_dump
}

do_deploy() {
	json_init

	if ! is_running; then
		json_add_boolean "success" 0
		json_add_string "error" "Container not running"
		json_dump
		return
	fi

	local repo=$(uci_get deploy.repo)
	if [ -z "$repo" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Deploy repository not configured"
		json_dump
		return
	fi

	# Run deploy in background
	"$HEXOCTL" exec sh -c "cd /opt/hexojs/site && hexo deploy" > /tmp/hexo-deploy.log 2>&1 &

	json_add_boolean "success" 1
	json_add_string "message" "Deploy started"
	json_add_string "log_file" "/tmp/hexo-deploy.log"

	json_dump
}

get_deploy_status() {
	json_init

	if pgrep -f "hexo deploy" >/dev/null 2>&1; then
		json_add_string "status" "running"
	else
		json_add_string "status" "idle"
	fi

	if [ -f /tmp/hexo-deploy.log ]; then
		local log=$(tail -20 /tmp/hexo-deploy.log 2>/dev/null)
		json_add_string "log" "$log"
	fi

	json_dump
}

# ============================================
# Preview Methods
# ============================================

preview_start() {
	json_init

	if ! is_running; then
		json_add_boolean "success" 0
		json_add_string "error" "Container not running. Start service first."
		json_dump
		return
	fi

	local http_port=$(uci_get main.http_port) || http_port="4000"

	json_add_boolean "success" 1
	json_add_string "message" "Preview server running"
	json_add_int "port" "$http_port"
	json_add_string "url" "http://$(uci -q get network.lan.ipaddr || echo "localhost"):$http_port"

	json_dump
}

preview_status() {
	json_init

	local running=$(is_running && echo 1 || echo 0)
	local http_port=$(uci_get main.http_port) || http_port="4000"

	json_add_boolean "running" "$running"
	json_add_int "port" "$http_port"

	if [ "$running" = "1" ]; then
		json_add_string "url" "http://$(uci -q get network.lan.ipaddr || echo "localhost"):$http_port"
	fi

	json_dump
}

# ============================================
# Configuration Methods
# ============================================

get_config() {
	json_init

	local enabled=$(uci_get main.enabled) || enabled="0"
	local http_port=$(uci_get main.http_port) || http_port="4000"
	local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
	local active_site=$(uci_get main.active_site) || active_site="default"
	local memory_limit=$(uci_get main.memory_limit) || memory_limit="512M"

	json_add_boolean "enabled" "$enabled"
	json_add_int "http_port" "$http_port"
	json_add_string "data_path" "$data_path"
	json_add_string "active_site" "$active_site"
	json_add_string "memory_limit" "$memory_limit"

	# Site config
	json_add_object "site"
	json_add_string "title" "$(uci_get ${active_site}.title)"
	json_add_string "subtitle" "$(uci_get ${active_site}.subtitle)"
	json_add_string "author" "$(uci_get ${active_site}.author)"
	json_add_string "language" "$(uci_get ${active_site}.language)"
	json_add_string "theme" "$(uci_get ${active_site}.theme)"
	json_add_string "url" "$(uci_get ${active_site}.url)"
	json_close_object

	# Deploy config
	json_add_object "deploy"
	json_add_string "type" "$(uci_get deploy.type)"
	json_add_string "repo" "$(uci_get deploy.repo)"
	json_add_string "branch" "$(uci_get deploy.branch)"
	json_close_object

	json_dump
}

save_config() {
	read input
	json_load "$input"

	json_get_var enabled enabled
	json_get_var http_port http_port
	json_get_var title title
	json_get_var subtitle subtitle
	json_get_var author author
	json_get_var language language
	json_get_var url url
	json_get_var deploy_repo deploy_repo
	json_get_var deploy_branch deploy_branch

	json_init

	local active_site=$(uci_get main.active_site) || active_site="default"

	[ -n "$enabled" ] && uci_set main.enabled "$enabled"
	[ -n "$http_port" ] && uci_set main.http_port "$http_port"
	[ -n "$title" ] && uci_set ${active_site}.title "$title"
	[ -n "$subtitle" ] && uci_set ${active_site}.subtitle "$subtitle"
	[ -n "$author" ] && uci_set ${active_site}.author "$author"
	[ -n "$language" ] && uci_set ${active_site}.language "$language"
	[ -n "$url" ] && uci_set ${active_site}.url "$url"
	[ -n "$deploy_repo" ] && uci_set deploy.repo "$deploy_repo"
	[ -n "$deploy_branch" ] && uci_set deploy.branch "$deploy_branch"

	# Update site _config.yml
	"$HEXOCTL" exec sh -c "cd /opt/hexojs && hexoctl update_site_config" 2>/dev/null || true

	json_add_boolean "success" 1
	json_add_string "message" "Configuration saved"

	json_dump
}

get_theme_config() {
	json_init

	json_add_string "default_mode" "$(uci_get theme_config.default_mode)"
	json_add_boolean "allow_toggle" "$(uci_get theme_config.allow_toggle)"
	json_add_string "accent_color" "$(uci_get theme_config.accent_color)"
	json_add_string "logo_symbol" "$(uci_get theme_config.logo_symbol)"
	json_add_string "logo_text" "$(uci_get theme_config.logo_text)"

	json_dump
}

save_theme_config() {
	read input
	json_load "$input"

	json_get_var default_mode default_mode
	json_get_var allow_toggle allow_toggle
	json_get_var accent_color accent_color
	json_get_var logo_symbol logo_symbol
	json_get_var logo_text logo_text

	json_init

	[ -n "$default_mode" ] && uci_set theme_config.default_mode "$default_mode"
	[ -n "$allow_toggle" ] && uci_set theme_config.allow_toggle "$allow_toggle"
	[ -n "$accent_color" ] && uci_set theme_config.accent_color "$accent_color"
	[ -n "$logo_symbol" ] && uci_set theme_config.logo_symbol "$logo_symbol"
	[ -n "$logo_text" ] && uci_set theme_config.logo_text "$logo_text"

	json_add_boolean "success" 1
	json_add_string "message" "Theme configuration saved"

	json_dump
}

list_presets() {
	json_init
	json_add_array "presets"

	local presets_dir="/usr/share/hexojs/presets"
	if [ -d "$presets_dir" ]; then
		for f in "$presets_dir"/*.yml; do
			[ -f "$f" ] || continue

			local filename=$(basename "$f")
			local id="${filename%.yml}"
			local name=$(grep -m1 "^name:" "$f" | sed 's/^name:[[:space:]]*//')
			local description=$(grep -m1 "^description:" "$f" | sed 's/^description:[[:space:]]*//')
			local icon=$(grep -m1 "^icon:" "$f" | sed 's/^icon:[[:space:]]*//')

			json_add_object
			json_add_string "id" "$id"
			json_add_string "name" "$name"
			json_add_string "description" "$description"
			json_add_string "icon" "$icon"
			json_close_object
		done
	fi

	json_close_array
	json_dump
}

apply_preset() {
	read input
	json_load "$input"
	json_get_var preset_id preset_id

	json_init

	if [ -z "$preset_id" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Preset ID required"
		json_dump
		return
	fi

	local preset_file="/usr/share/hexojs/presets/${preset_id}.yml"
	if [ ! -f "$preset_file" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Preset not found"
		json_dump
		return
	fi

	local site_path=$(get_site_path)
	local theme_config="$site_path/themes/cybermind/_config.yml"

	if [ -f "$theme_config" ]; then
		cp "$preset_file" "$theme_config"
	fi

	json_add_boolean "success" 1
	json_add_string "message" "Preset applied"

	json_dump
}

# ============================================
# GitHub Sync Methods
# ============================================

git_status() {
	local site_path=$(get_site_path)

	json_init

	if [ ! -d "$site_path" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Site not found"
		json_dump
		return
	fi

	cd "$site_path" 2>/dev/null || {
		json_add_boolean "success" 0
		json_add_string "error" "Cannot access site directory"
		json_dump
		return
	}

	# Check if it's a git repo
	if [ ! -d "$site_path/.git" ]; then
		json_add_boolean "success" 1
		json_add_boolean "is_repo" 0
		json_add_string "message" "Not a git repository"
		json_dump
		return
	fi

	json_add_boolean "success" 1
	json_add_boolean "is_repo" 1

	# Get remote URL
	local remote=$(git remote get-url origin 2>/dev/null || echo "")
	json_add_string "remote" "$remote"

	# Get current branch
	local branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
	json_add_string "branch" "$branch"

	# Get status summary
	local modified=$(git status --porcelain 2>/dev/null | grep -c '^.M' || echo 0)
	local untracked=$(git status --porcelain 2>/dev/null | grep -c '^??' || echo 0)
	local staged=$(git status --porcelain 2>/dev/null | grep -c '^M' || echo 0)

	json_add_int "modified" "$modified"
	json_add_int "untracked" "$untracked"
	json_add_int "staged" "$staged"

	# Check for unpushed commits
	local ahead=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo 0)
	local behind=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo 0)

	json_add_int "ahead" "$ahead"
	json_add_int "behind" "$behind"

	# Last commit info
	local last_commit=$(git log -1 --format="%h - %s (%cr)" 2>/dev/null || echo "No commits")
	json_add_string "last_commit" "$last_commit"

	# Full status output
	local status_output=$(git status --short 2>/dev/null | head -20)
	json_add_string "status_output" "$status_output"

	json_dump
}

git_init() {
	read input
	json_load "$input"
	json_get_var repo repo
	json_get_var branch branch

	json_init

	local site_path=$(get_site_path)

	if [ -z "$repo" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Repository URL required"
		json_dump
		return
	fi

	[ -z "$branch" ] && branch="main"

	# Initialize git if not already a repo
	if [ ! -d "$site_path/.git" ]; then
		cd "$site_path" && git init >/dev/null 2>&1
	fi

	cd "$site_path"

	# Remove existing origin if present
	git remote remove origin 2>/dev/null || true

	# Add remote
	git remote add origin "$repo"

	# Set default branch
	git branch -M "$branch" 2>/dev/null || true

	json_add_boolean "success" 1
	json_add_string "message" "Repository initialized with remote: $repo"

	json_dump
}

git_clone() {
	read input
	json_load "$input"
	json_get_var repo repo
	json_get_var branch branch

	json_init

	if [ -z "$repo" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Repository URL required"
		json_dump
		return
	fi

	[ -z "$branch" ] && branch="main"

	local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
	local site_path="$data_path/site"

	# Backup existing site if present
	if [ -d "$site_path" ]; then
		local backup="$data_path/site.backup.$(date +%Y%m%d%H%M%S)"
		mv "$site_path" "$backup"
	fi

	# Clone repository
	local output=$(git clone --branch "$branch" --single-branch "$repo" "$site_path" 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Repository cloned successfully"
		json_add_string "branch" "$branch"
	else
		# Restore backup on failure
		[ -d "$backup" ] && mv "$backup" "$site_path"
		json_add_boolean "success" 0
		json_add_string "error" "Clone failed: $output"
	fi

	json_dump
}

git_pull() {
	json_init

	local site_path=$(get_site_path)

	if [ ! -d "$site_path/.git" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Not a git repository"
		json_dump
		return
	fi

	cd "$site_path"

	# Stash local changes first
	git stash push -m "auto-stash-$(date +%Y%m%d%H%M%S)" 2>/dev/null

	# Pull changes
	local output=$(git pull --rebase 2>&1)
	local result=$?

	# Pop stash if it was created
	git stash pop 2>/dev/null || true

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Pull successful"
		json_add_string "output" "$output"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Pull failed: $output"
	fi

	json_dump
}

git_push() {
	read input
	json_load "$input"
	json_get_var message message
	json_get_var force force

	json_init

	local site_path=$(get_site_path)

	if [ ! -d "$site_path/.git" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Not a git repository"
		json_dump
		return
	fi

	cd "$site_path"

	# Check if there's a remote
	local remote=$(git remote get-url origin 2>/dev/null || echo "")
	if [ -z "$remote" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "No remote repository configured"
		json_dump
		return
	fi

	# Stage all changes
	git add -A

	# Commit if there are staged changes
	local staged=$(git diff --cached --quiet; echo $?)
	if [ "$staged" = "1" ]; then
		local commit_msg="${message:-Auto-commit from SecuBox CMS $(date +%Y-%m-%d\ %H:%M:%S)}"
		git commit -m "$commit_msg" 2>/dev/null
	fi

	# Push changes
	local push_args=""
	[ "$force" = "1" ] && push_args="--force"

	local output=$(git push $push_args origin HEAD 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Push successful"
		json_add_string "output" "$output"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Push failed: $output"
	fi

	json_dump
}

git_fetch() {
	json_init

	local site_path=$(get_site_path)

	if [ ! -d "$site_path/.git" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Not a git repository"
		json_dump
		return
	fi

	cd "$site_path"

	local output=$(git fetch --all 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Fetch successful"
		json_add_string "output" "$output"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Fetch failed: $output"
	fi

	json_dump
}

git_log() {
	json_init

	local site_path=$(get_site_path)
	local tmp_log="/tmp/hexojs_git_log_$$"

	if [ ! -d "$site_path/.git" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Not a git repository"
		json_dump
		return
	fi

	cd "$site_path"

	json_add_boolean "success" 1
	json_add_array "commits"

	# Save git log to temp file to avoid piped while loop
	git log --oneline -20 2>/dev/null > "$tmp_log"

	while read line; do
		local hash=$(echo "$line" | cut -d' ' -f1)
		local msg=$(echo "$line" | cut -d' ' -f2-)

		json_add_object
		json_add_string "hash" "$hash"
		json_add_string "message" "$msg"
		json_close_object
	done < "$tmp_log"

	rm -f "$tmp_log"

	json_close_array
	json_dump
}

git_reset() {
	read input
	json_load "$input"
	json_get_var hard hard

	json_init

	local site_path=$(get_site_path)

	if [ ! -d "$site_path/.git" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Not a git repository"
		json_dump
		return
	fi

	cd "$site_path"

	if [ "$hard" = "1" ]; then
		local output=$(git reset --hard HEAD 2>&1)
		local result=$?

		if [ "$result" -eq 0 ]; then
			git clean -fd 2>/dev/null
			json_add_boolean "success" 1
			json_add_string "message" "Hard reset complete. All local changes discarded."
		else
			json_add_boolean "success" 0
			json_add_string "error" "Reset failed: $output"
		fi
	else
		git reset HEAD 2>/dev/null
		json_add_boolean "success" 1
		json_add_string "message" "Unstaged all changes"
	fi

	json_dump
}

git_set_credentials() {
	read input
	json_load "$input"
	json_get_var name name
	json_get_var email email

	json_init

	local site_path=$(get_site_path)

	if [ ! -d "$site_path/.git" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Not a git repository"
		json_dump
		return
	fi

	cd "$site_path"

	[ -n "$name" ] && git config user.name "$name"
	[ -n "$email" ] && git config user.email "$email"

	json_add_boolean "success" 1
	json_add_string "message" "Git credentials configured"

	json_dump
}

git_get_credentials() {
	json_init

	local site_path=$(get_site_path)

	if [ ! -d "$site_path/.git" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Not a git repository"
		json_dump
		return
	fi

	cd "$site_path"

	local name=$(git config user.name 2>/dev/null || echo "")
	local email=$(git config user.email 2>/dev/null || echo "")

	json_add_boolean "success" 1
	json_add_string "name" "$name"
	json_add_string "email" "$email"

	json_dump
}

# ============================================
# Gitea Integration Methods
# ============================================

gitea_status() {
	json_init

	local enabled=$(uci -q get hexojs.gitea.enabled) || enabled="0"
	local gitea_url=$(uci -q get hexojs.gitea.url) || gitea_url=""
	local gitea_user=$(uci -q get hexojs.gitea.user) || gitea_user=""
	local content_repo=$(uci -q get hexojs.gitea.content_repo) || content_repo=""
	local content_branch=$(uci -q get hexojs.gitea.content_branch) || content_branch="main"
	local auto_sync=$(uci -q get hexojs.gitea.auto_sync) || auto_sync="0"

	local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
	local content_path="$data_path/content"
	local has_repo="false"
	local last_commit=""
	local branch=""

	if [ -d "$content_path/.git" ]; then
		has_repo="true"
		cd "$content_path"
		last_commit=$(git log -1 --format="%h %s" 2>/dev/null || echo "unknown")
		branch=$(git branch --show-current 2>/dev/null || echo "unknown")
	fi

	json_add_boolean "enabled" "$enabled"
	json_add_string "gitea_url" "$gitea_url"
	json_add_string "gitea_user" "$gitea_user"
	json_add_string "content_repo" "$content_repo"
	json_add_string "content_branch" "$content_branch"
	json_add_boolean "auto_sync" "$auto_sync"
	json_add_boolean "has_local_repo" "$([ "$has_repo" = "true" ] && echo 1 || echo 0)"
	json_add_string "local_branch" "$branch"
	json_add_string "last_commit" "$last_commit"

	json_dump
}

gitea_setup() {
	json_init

	local output=$("$HEXOCTL" gitea setup 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Git credentials configured"
	else
		json_add_boolean "success" 0
		json_add_string "error" "$output"
	fi

	json_dump
}

gitea_clone() {
	json_init

	local output=$("$HEXOCTL" gitea clone 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Content cloned from Gitea"
	else
		json_add_boolean "success" 0
		json_add_string "error" "$output"
	fi

	json_dump
}

gitea_sync() {
	json_init

	local output=$("$HEXOCTL" gitea sync 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Content synced from Gitea"
	else
		json_add_boolean "success" 0
		json_add_string "error" "$output"
	fi

	json_dump
}

gitea_save_config() {
	read input
	json_load "$input"

	json_get_var enabled enabled
	json_get_var gitea_url gitea_url
	json_get_var gitea_user gitea_user
	json_get_var gitea_token gitea_token
	json_get_var content_repo content_repo
	json_get_var content_branch content_branch
	json_get_var auto_sync auto_sync

	json_init

	[ -n "$enabled" ] && uci set hexojs.gitea.enabled="$enabled"
	[ -n "$gitea_url" ] && uci set hexojs.gitea.url="$gitea_url"
	[ -n "$gitea_user" ] && uci set hexojs.gitea.user="$gitea_user"
	[ -n "$gitea_token" ] && uci set hexojs.gitea.token="$gitea_token"
	[ -n "$content_repo" ] && uci set hexojs.gitea.content_repo="$content_repo"
	[ -n "$content_branch" ] && uci set hexojs.gitea.content_branch="$content_branch"
	[ -n "$auto_sync" ] && uci set hexojs.gitea.auto_sync="$auto_sync"

	uci commit hexojs

	json_add_boolean "success" 1
	json_add_string "message" "Gitea configuration saved"

	json_dump
}

# ============================================
# Workflow Status (Gitea → Hexo → HAProxy)
# ============================================

get_workflow_status() {
	json_init

	local site_path=$(get_site_path)
	local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"

	# ── Gitea Integration Status ──
	json_add_object "gitea"

	local gitea_enabled=$(uci -q get hexojs.gitea.enabled) || gitea_enabled="0"
	local gitea_url=$(uci -q get hexojs.gitea.url) || gitea_url=""
	local content_repo=$(uci -q get hexojs.gitea.content_repo) || content_repo=""
	local auto_sync=$(uci -q get hexojs.gitea.auto_sync) || auto_sync="0"

	json_add_boolean "enabled" "$gitea_enabled"
	json_add_string "url" "$gitea_url"
	json_add_string "repo" "$content_repo"
	json_add_boolean "auto_sync" "$auto_sync"

	# Check git repo status
	if [ -d "$site_path/.git" ]; then
		cd "$site_path"
		local branch=$(git branch --show-current 2>/dev/null || echo "")
		local remote=$(git remote get-url origin 2>/dev/null || echo "")
		local last_commit=$(git log -1 --format="%h - %s" 2>/dev/null || echo "")
		local last_commit_date=$(git log -1 --format="%ci" 2>/dev/null || echo "")
		local ahead=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo "0")
		local behind=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo "0")
		local modified=$(git status --porcelain 2>/dev/null | grep -c '^.M' || echo "0")
		local untracked=$(git status --porcelain 2>/dev/null | grep -c '^??' || echo "0")

		json_add_boolean "has_repo" 1
		json_add_string "branch" "$branch"
		json_add_string "remote" "$remote"
		json_add_string "last_commit" "$last_commit"
		json_add_string "last_commit_date" "$last_commit_date"
		json_add_int "ahead" "$ahead"
		json_add_int "behind" "$behind"
		json_add_int "modified" "$modified"
		json_add_int "untracked" "$untracked"

		# Sync status
		if [ "$behind" -gt 0 ]; then
			json_add_string "sync_status" "behind"
			json_add_string "sync_message" "Pull required ($behind commits behind)"
		elif [ "$ahead" -gt 0 ]; then
			json_add_string "sync_status" "ahead"
			json_add_string "sync_message" "Push available ($ahead commits ahead)"
		elif [ "$modified" -gt 0 ] || [ "$untracked" -gt 0 ]; then
			json_add_string "sync_status" "modified"
			json_add_string "sync_message" "Local changes pending"
		else
			json_add_string "sync_status" "synced"
			json_add_string "sync_message" "Up to date"
		fi
	else
		json_add_boolean "has_repo" 0
		json_add_string "sync_status" "not_initialized"
		json_add_string "sync_message" "Git repository not configured"
	fi

	json_close_object

	# ── Hexo Build Status ──
	json_add_object "hexo"

	local running=$(is_running && echo 1 || echo 0)
	local http_port=$(uci_get main.http_port) || http_port="4000"
	local post_count=0
	local draft_count=0

	[ -d "$site_path/source/_posts" ] && post_count=$(find "$site_path/source/_posts" -type f -name "*.md" ! -name "index.md" 2>/dev/null | wc -l)
	[ -d "$site_path/source/_drafts" ] && draft_count=$(find "$site_path/source/_drafts" -type f -name "*.md" ! -name "index.md" 2>/dev/null | wc -l)

	json_add_boolean "container_running" "$running"
	json_add_int "http_port" "$http_port"
	json_add_int "post_count" "$post_count"
	json_add_int "draft_count" "$draft_count"

	# Check if public/ exists and its freshness
	local public_dir="$site_path/public"
	if [ -d "$public_dir" ]; then
		json_add_boolean "site_built" 1
		local public_files=$(find "$public_dir" -type f 2>/dev/null | wc -l)
		json_add_int "public_files" "$public_files"

		# Check build freshness
		local newest_post=$(find "$site_path/source/_posts" -type f -name "*.md" -printf '%T@\n' 2>/dev/null | sort -rn | head -1)
		local newest_public=$(find "$public_dir" -type f -name "*.html" -printf '%T@\n' 2>/dev/null | sort -rn | head -1)

		if [ -n "$newest_post" ] && [ -n "$newest_public" ]; then
			if [ "${newest_post%.*}" -gt "${newest_public%.*}" ]; then
				json_add_string "build_status" "outdated"
				json_add_string "build_message" "Rebuild required (source newer than build)"
			else
				json_add_string "build_status" "current"
				json_add_string "build_message" "Build is up to date"
			fi
		else
			json_add_string "build_status" "unknown"
			json_add_string "build_message" "Unable to determine build freshness"
		fi
	else
		json_add_boolean "site_built" 0
		json_add_int "public_files" 0
		json_add_string "build_status" "not_built"
		json_add_string "build_message" "Site has not been generated"
	fi

	json_close_object

	# ── HAProxy Publishing Status ──
	json_add_object "haproxy"

	local site_url=$(uci -q get hexojs.default.url) || site_url=""
	local domain=""
	# Extract domain from URL
	if [ -n "$site_url" ]; then
		domain=$(echo "$site_url" | sed 's|^https\?://||' | sed 's|/.*$||')
	fi

	json_add_string "site_url" "$site_url"
	json_add_string "domain" "$domain"

	# Check HAProxy vhost existence
	local haproxy_running=$(pgrep haproxy >/dev/null 2>&1 && echo 1 || echo 0)
	json_add_boolean "running" "$haproxy_running"

	if [ -n "$domain" ]; then
		# Check if vhost exists via ubus
		local vhost_exists=$(ubus call luci.haproxy list_vhosts 2>/dev/null | grep -q "\"$domain\"" && echo 1 || echo 0)
		json_add_boolean "vhost_configured" "$vhost_exists"

		# Check certificate status
		local cert_status=""
		local cert_file="/etc/haproxy/certs/${domain}.pem"
		if [ -f "$cert_file" ]; then
			local expiry=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | sed 's/notAfter=//')
			if [ -n "$expiry" ]; then
				local expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null || echo 0)
				local now_epoch=$(date +%s)
				local days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

				if [ "$days_left" -lt 0 ]; then
					cert_status="expired"
				elif [ "$days_left" -lt 7 ]; then
					cert_status="critical"
				elif [ "$days_left" -lt 30 ]; then
					cert_status="expiring"
				else
					cert_status="valid"
				fi
				json_add_int "cert_days_left" "$days_left"
			fi
		else
			cert_status="missing"
		fi
		json_add_string "cert_status" "$cert_status"

		# Publishing status
		if [ "$vhost_exists" = "1" ] && [ "$cert_status" = "valid" ]; then
			json_add_string "publish_status" "published"
			json_add_string "publish_message" "Live at https://$domain"
		elif [ "$vhost_exists" = "1" ]; then
			json_add_string "publish_status" "partial"
			json_add_string "publish_message" "Vhost configured, certificate issue"
		else
			json_add_string "publish_status" "not_published"
			json_add_string "publish_message" "Not published to HAProxy"
		fi
	else
		json_add_boolean "vhost_configured" 0
		json_add_string "cert_status" "no_domain"
		json_add_string "publish_status" "not_configured"
		json_add_string "publish_message" "No domain configured"
	fi

	json_close_object

	# ── Portal Status ──
	json_add_object "portal"

	local portal_path=$(uci -q get hexojs.portal.path) || portal_path="/www/blog"
	json_add_string "path" "$portal_path"

	if [ -d "$portal_path" ]; then
		local portal_files=$(find "$portal_path" -type f 2>/dev/null | wc -l)
		json_add_boolean "deployed" 1
		json_add_int "file_count" "$portal_files"

		# Check portal freshness vs public/
		local portal_mtime=$(stat -c %Y "$portal_path/index.html" 2>/dev/null || echo 0)
		local public_mtime=$(stat -c %Y "$site_path/public/index.html" 2>/dev/null || echo 0)

		if [ "$public_mtime" -gt "$portal_mtime" ]; then
			json_add_string "deploy_status" "outdated"
			json_add_string "deploy_message" "Portal needs republishing"
		else
			json_add_string "deploy_status" "current"
			json_add_string "deploy_message" "Portal is up to date"
		fi
	else
		json_add_boolean "deployed" 0
		json_add_int "file_count" 0
		json_add_string "deploy_status" "not_deployed"
		json_add_string "deploy_message" "Not deployed to portal"
	fi

	json_close_object

	# ── Overall Workflow Status ──
	json_add_object "workflow"

	# Calculate overall health
	local issues=0
	local warnings=0

	# Check each stage
	[ ! -d "$site_path/.git" ] && issues=$((issues + 1))
	[ "$running" != "1" ] && issues=$((issues + 1))
	[ ! -d "$public_dir" ] && issues=$((issues + 1))

	if [ "$issues" -eq 0 ]; then
		json_add_string "status" "healthy"
		json_add_string "message" "All workflow stages operational"
	elif [ "$issues" -eq 1 ]; then
		json_add_string "status" "warning"
		json_add_string "message" "One workflow stage needs attention"
	else
		json_add_string "status" "critical"
		json_add_string "message" "Multiple workflow stages need attention"
	fi

	json_add_int "issues" "$issues"

	json_close_object

	json_dump
}

# Publish to /www (portal)
publish_to_www() {
	read input
	json_load "$input"
	json_get_var path path

	json_init

	if ! is_running; then
		json_add_boolean "success" 0
		json_add_string "error" "Container not running"
		json_dump
		return
	fi

	# Default path is /www/blog
	[ -z "$path" ] && path="/www/blog"

	# Set portal path in UCI for hexoctl
	uci set hexojs.portal=hexojs
	uci set hexojs.portal.path="$path"
	uci commit hexojs

	# Run publish command
	local output=$("$HEXOCTL" publish 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Published to $path"
		json_add_string "path" "$path"
	else
		json_add_boolean "success" 0
		json_add_string "error" "$output"
	fi

	json_dump
}

# ============================================
# Publishing Profiles (Wizard)
# ============================================

# List available publishing profiles
list_profiles() {
	json_init
	json_add_array "profiles"

	# Blog Profile
	json_add_object
	json_add_string "id" "blog"
	json_add_string "name" "Blog"
	json_add_string "description" "Personal blog with public domain and optional Tor"
	json_add_string "icon" "📝"
	json_add_boolean "haproxy" 1
	json_add_boolean "tor" 0
	json_add_boolean "acme" 1
	json_add_int "default_port" 4000
	json_close_object

	# Portfolio Profile
	json_add_object
	json_add_string "id" "portfolio"
	json_add_string "name" "Portfolio"
	json_add_string "description" "Professional portfolio with SSL"
	json_add_string "icon" "💼"
	json_add_boolean "haproxy" 1
	json_add_boolean "tor" 0
	json_add_boolean "acme" 1
	json_add_int "default_port" 4001
	json_close_object

	# Privacy Blog Profile
	json_add_object
	json_add_string "id" "privacy"
	json_add_string "name" "Privacy Blog"
	json_add_string "description" "Tor-only hidden service blog"
	json_add_string "icon" "🧅"
	json_add_boolean "haproxy" 0
	json_add_boolean "tor" 1
	json_add_boolean "acme" 0
	json_add_int "default_port" 4002
	json_close_object

	# Dual Access Profile
	json_add_object
	json_add_string "id" "dual"
	json_add_string "name" "Dual Access"
	json_add_string "description" "Public domain + Tor hidden service"
	json_add_string "icon" "🌐"
	json_add_boolean "haproxy" 1
	json_add_boolean "tor" 1
	json_add_boolean "acme" 1
	json_add_int "default_port" 4003
	json_close_object

	# Documentation Profile
	json_add_object
	json_add_string "id" "docs"
	json_add_string "name" "Documentation"
	json_add_string "description" "Internal documentation site"
	json_add_string "icon" "📚"
	json_add_boolean "haproxy" 1
	json_add_boolean "tor" 0
	json_add_boolean "acme" 0
	json_add_int "default_port" 4004
	json_close_object

	json_close_array
	json_dump
}

# Apply a publishing profile to an instance
apply_profile() {
	read input
	json_load "$input"
	json_get_var instance instance
	json_get_var profile profile
	json_get_var domain domain
	json_get_var enable_tor enable_tor

	json_init

	if [ -z "$instance" ] || [ -z "$profile" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance and profile required"
		json_dump
		return
	fi

	local port=$(uci -q get hexojs.${instance}.port)
	if [ -z "$port" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance not found: $instance"
		json_dump
		return
	fi

	local results=""

	# Apply profile settings
	case "$profile" in
		blog|portfolio|dual|docs)
			if [ -n "$domain" ]; then
				# Create HAProxy backend and vhost
				local haproxy_result=$(create_haproxy_vhost "$instance" "$domain" "$port" 1)
				results="$results HAProxy:$haproxy_result"
				uci set hexojs.${instance}.domain="$domain"
			fi
			;;
		privacy)
			enable_tor="1"
			;;
	esac

	# Enable Tor if requested
	if [ "$enable_tor" = "1" ] || [ "$profile" = "privacy" ] || [ "$profile" = "dual" ]; then
		local tor_result=$(create_tor_hidden_service "$instance" "$port")
		results="$results Tor:$tor_result"
		uci set hexojs.${instance}.tor_enabled="1"
	fi

	uci set hexojs.${instance}.profile="$profile"
	uci commit hexojs

	json_add_boolean "success" 1
	json_add_string "message" "Profile '$profile' applied to instance '$instance'"
	json_add_string "results" "$results"

	json_dump
}

# ============================================
# HAProxy Integration
# ============================================

# Internal: Create HAProxy vhost for instance
create_haproxy_vhost() {
	local instance="$1"
	local domain="$2"
	local port="$3"
	local acme="$4"

	# Check if HAProxy RPCD is available
	if ! ubus list | grep -q "luci.haproxy"; then
		echo "haproxy_unavailable"
		return 1
	fi

	# Create backend
	ubus call luci.haproxy create_backend \
		"{\"name\":\"hexo_${instance}\",\"mode\":\"http\"}" 2>/dev/null

	# Create server in backend
	ubus call luci.haproxy create_server \
		"{\"backend\":\"hexo_${instance}\",\"name\":\"${instance}\",\"address\":\"127.0.0.1\",\"port\":${port}}" 2>/dev/null

	# Create vhost
	local vhost_params="{\"domain\":\"${domain}\",\"backend\":\"hexo_${instance}\",\"ssl\":true,\"ssl_redirect\":true"
	[ "$acme" = "1" ] && vhost_params="${vhost_params},\"acme\":true"
	vhost_params="${vhost_params}}"

	ubus call luci.haproxy create_vhost "$vhost_params" 2>/dev/null

	# Request certificate if ACME enabled
	if [ "$acme" = "1" ]; then
		ubus call luci.haproxy request_certificate "{\"domain\":\"${domain}\"}" 2>/dev/null &
	fi

	echo "ok"
	return 0
}

# Publish instance to HAProxy
publish_to_haproxy() {
	read input
	json_load "$input"
	json_get_var instance instance
	json_get_var domain domain
	json_get_var acme acme

	json_init

	if [ -z "$instance" ] || [ -z "$domain" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance and domain required"
		json_dump
		return
	fi

	local port=$(uci -q get hexojs.${instance}.port)
	if [ -z "$port" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance not found: $instance"
		json_dump
		return
	fi

	[ -z "$acme" ] && acme="1"

	local result=$(create_haproxy_vhost "$instance" "$domain" "$port" "$acme")

	if [ "$result" = "ok" ]; then
		uci set hexojs.${instance}.domain="$domain"
		uci set hexojs.${instance}.haproxy_enabled="1"
		uci commit hexojs

		json_add_boolean "success" 1
		json_add_string "message" "Published to HAProxy"
		json_add_string "domain" "$domain"
		json_add_string "url" "https://${domain}"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Failed to create HAProxy vhost: $result"
	fi

	json_dump
}

# Unpublish from HAProxy
unpublish_from_haproxy() {
	read input
	json_load "$input"
	json_get_var instance instance

	json_init

	if [ -z "$instance" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance required"
		json_dump
		return
	fi

	local domain=$(uci -q get hexojs.${instance}.domain)

	if [ -n "$domain" ]; then
		# Delete vhost
		ubus call luci.haproxy delete_vhost "{\"domain\":\"${domain}\"}" 2>/dev/null
		# Delete backend
		ubus call luci.haproxy delete_backend "{\"name\":\"hexo_${instance}\"}" 2>/dev/null
	fi

	uci delete hexojs.${instance}.domain 2>/dev/null
	uci set hexojs.${instance}.haproxy_enabled="0"
	uci commit hexojs

	json_add_boolean "success" 1
	json_add_string "message" "Unpublished from HAProxy"

	json_dump
}

# Get HAProxy status for instance
get_haproxy_status() {
	read input
	json_load "$input"
	json_get_var instance instance

	json_init

	if [ -z "$instance" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance required"
		json_dump
		return
	fi

	local domain=$(uci -q get hexojs.${instance}.domain)
	local port=$(uci -q get hexojs.${instance}.port)

	json_add_boolean "success" 1
	json_add_string "instance" "$instance"
	json_add_string "domain" "$domain"
	json_add_int "port" "$port"

	if [ -n "$domain" ]; then
		json_add_boolean "published" 1
		json_add_string "url" "https://${domain}"

		# Check certificate
		local cert_file="/etc/haproxy/certs/${domain}.pem"
		if [ -f "$cert_file" ]; then
			local expiry=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | sed 's/notAfter=//')
			if [ -n "$expiry" ]; then
				local expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null || echo 0)
				local now_epoch=$(date +%s)
				local days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
				json_add_string "cert_status" "valid"
				json_add_int "cert_days_left" "$days_left"
				json_add_string "cert_expiry" "$expiry"
			fi
		else
			json_add_string "cert_status" "missing"
		fi

		# Check DNS
		local resolved_ip=$(nslookup "$domain" 2>/dev/null | grep -A1 "Name:" | grep "Address:" | awk '{print $2}' | head -1)
		if [ -n "$resolved_ip" ]; then
			json_add_string "dns_status" "ok"
			json_add_string "dns_resolved_ip" "$resolved_ip"
		else
			json_add_string "dns_status" "failed"
		fi
	else
		json_add_boolean "published" 0
	fi

	json_dump
}

# ============================================
# Tor Hidden Service Integration
# ============================================

# Internal: Create Tor hidden service
create_tor_hidden_service() {
	local instance="$1"
	local port="$2"

	# Check if Tor Shield RPCD is available
	if ! ubus list | grep -q "luci.tor-shield"; then
		echo "tor_unavailable"
		return 1
	fi

	# Create hidden service
	ubus call luci.tor-shield add_hidden_service \
		"{\"name\":\"hexo_${instance}\",\"local_port\":${port},\"virtual_port\":80}" 2>/dev/null

	# Wait for onion address (up to 10 seconds)
	local onion=""
	local tries=0
	while [ -z "$onion" ] && [ "$tries" -lt 10 ]; do
		sleep 1
		onion=$(cat /var/lib/tor/hidden_service_hexo_${instance}/hostname 2>/dev/null)
		tries=$((tries + 1))
	done

	if [ -n "$onion" ]; then
		echo "$onion"
		return 0
	else
		echo "pending"
		return 0
	fi
}

# Publish instance to Tor
publish_to_tor() {
	read input
	json_load "$input"
	json_get_var instance instance

	json_init

	if [ -z "$instance" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance required"
		json_dump
		return
	fi

	local port=$(uci -q get hexojs.${instance}.port)
	if [ -z "$port" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance not found: $instance"
		json_dump
		return
	fi

	local onion=$(create_tor_hidden_service "$instance" "$port")

	if [ "$onion" != "tor_unavailable" ]; then
		uci set hexojs.${instance}.tor_enabled="1"
		[ "$onion" != "pending" ] && uci set hexojs.${instance}.onion_address="$onion"
		uci commit hexojs

		json_add_boolean "success" 1
		json_add_string "message" "Tor hidden service created"

		if [ "$onion" != "pending" ]; then
			json_add_string "onion_address" "$onion"
			json_add_string "url" "http://${onion}"
		else
			json_add_string "status" "pending"
			json_add_string "message" "Onion address generating, check back soon"
		fi
	else
		json_add_boolean "success" 0
		json_add_string "error" "Tor Shield not available"
	fi

	json_dump
}

# Unpublish from Tor
unpublish_from_tor() {
	read input
	json_load "$input"
	json_get_var instance instance

	json_init

	if [ -z "$instance" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance required"
		json_dump
		return
	fi

	# Remove hidden service
	ubus call luci.tor-shield remove_hidden_service "{\"name\":\"hexo_${instance}\"}" 2>/dev/null

	uci delete hexojs.${instance}.onion_address 2>/dev/null
	uci set hexojs.${instance}.tor_enabled="0"
	uci commit hexojs

	json_add_boolean "success" 1
	json_add_string "message" "Unpublished from Tor"

	json_dump
}

# Get Tor status for instance
get_tor_status() {
	read input
	json_load "$input"
	json_get_var instance instance

	json_init

	if [ -z "$instance" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance required"
		json_dump
		return
	fi

	local tor_enabled=$(uci -q get hexojs.${instance}.tor_enabled)
	local onion=$(uci -q get hexojs.${instance}.onion_address)

	# Try to get onion from file if not in UCI
	if [ -z "$onion" ]; then
		onion=$(cat /var/lib/tor/hidden_service_hexo_${instance}/hostname 2>/dev/null)
		[ -n "$onion" ] && uci set hexojs.${instance}.onion_address="$onion" && uci commit hexojs
	fi

	json_add_boolean "success" 1
	json_add_string "instance" "$instance"
	json_add_boolean "enabled" "${tor_enabled:-0}"

	if [ -n "$onion" ]; then
		json_add_string "onion_address" "$onion"
		json_add_string "url" "http://${onion}"
		json_add_string "status" "active"
	elif [ "$tor_enabled" = "1" ]; then
		json_add_string "status" "pending"
	else
		json_add_string "status" "disabled"
	fi

	json_dump
}

# ============================================
# Full Publishing Pipeline
# ============================================

# Get all publishing endpoints for an instance
get_instance_endpoints() {
	read input
	json_load "$input"
	json_get_var instance instance

	json_init

	if [ -z "$instance" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance required"
		json_dump
		return
	fi

	local port=$(uci -q get hexojs.${instance}.port)
	local domain=$(uci -q get hexojs.${instance}.domain)
	local onion=$(uci -q get hexojs.${instance}.onion_address)
	local lan_ip=$(uci -q get network.lan.ipaddr || echo "192.168.255.1")

	json_add_boolean "success" 1
	json_add_string "instance" "$instance"

	json_add_array "endpoints"

	# Local endpoint
	json_add_object
	json_add_string "type" "local"
	json_add_string "url" "http://${lan_ip}:${port}"
	json_add_string "status" "active"
	json_add_string "icon" "🏠"
	json_close_object

	# HAProxy/clearnet endpoint
	if [ -n "$domain" ]; then
		json_add_object
		json_add_string "type" "clearnet"
		json_add_string "url" "https://${domain}"
		json_add_string "domain" "$domain"

		# Check health
		local cert_file="/etc/haproxy/certs/${domain}.pem"
		if [ -f "$cert_file" ]; then
			json_add_string "status" "active"
			json_add_string "ssl" "valid"
		else
			json_add_string "status" "no_cert"
			json_add_string "ssl" "missing"
		fi
		json_add_string "icon" "🌐"
		json_close_object
	fi

	# Tor endpoint
	if [ -n "$onion" ]; then
		json_add_object
		json_add_string "type" "tor"
		json_add_string "url" "http://${onion}"
		json_add_string "onion" "$onion"
		json_add_string "status" "active"
		json_add_string "icon" "🧅"
		json_close_object
	fi

	json_close_array
	json_dump
}

# Full publish pipeline - build, deploy to all configured endpoints
full_publish() {
	read input
	json_load "$input"
	json_get_var instance instance
	json_get_var rebuild rebuild

	json_init

	if [ -z "$instance" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance required"
		json_dump
		return
	fi

	local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
	local site_path="$data_path/instances/$instance/site"

	json_add_array "steps"

	# Step 1: Rebuild if requested or needed
	if [ "$rebuild" = "1" ] || [ ! -d "$site_path/public" ]; then
		json_add_object
		json_add_string "step" "build"
		json_add_string "status" "running"
		json_close_object

		local build_output=$("$HEXOCTL" build "$instance" 2>&1)
		local build_result=$?

		json_add_object
		json_add_string "step" "build"
		if [ "$build_result" -eq 0 ]; then
			json_add_string "status" "success"
		else
			json_add_string "status" "failed"
			json_add_string "error" "$build_output"
		fi
		json_close_object
	fi

	# Step 2: Refresh HAProxy if configured
	local domain=$(uci -q get hexojs.${instance}.domain)
	if [ -n "$domain" ]; then
		json_add_object
		json_add_string "step" "haproxy"
		json_add_string "status" "active"
		json_add_string "url" "https://${domain}"
		json_close_object
	fi

	# Step 3: Refresh Tor if configured
	local onion=$(uci -q get hexojs.${instance}.onion_address)
	if [ -n "$onion" ]; then
		json_add_object
		json_add_string "step" "tor"
		json_add_string "status" "active"
		json_add_string "url" "http://${onion}"
		json_close_object
	fi

	json_close_array

	json_add_boolean "success" 1
	json_add_string "message" "Publishing pipeline complete"

	json_dump
}

# ============================================
# Gitea Webhook Handler
# ============================================

# Handle Gitea webhook push event
handle_webhook() {
	read input
	json_load "$input"
	json_get_var event event
	json_get_var repository repository
	json_get_var ref ref
	json_get_var secret secret

	json_init

	# Verify webhook secret if configured
	local configured_secret=$(uci -q get hexojs.gitea.webhook_secret)
	if [ -n "$configured_secret" ] && [ "$secret" != "$configured_secret" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Invalid webhook secret"
		json_dump
		return
	fi

	# Only handle push events
	if [ "$event" != "push" ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Event '$event' ignored"
		json_dump
		return
	fi

	local content_repo=$(uci -q get hexojs.gitea.content_repo)

	# Check if this is our content repo
	if [ -n "$repository" ] && [ "$repository" != "$content_repo" ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Repository '$repository' not configured for sync"
		json_dump
		return
	fi

	# Trigger sync and rebuild
	local sync_output=$("$HEXOCTL" gitea sync 2>&1)
	local sync_result=$?

	if [ "$sync_result" -eq 0 ]; then
		# Auto-rebuild if configured
		local auto_build=$(uci -q get hexojs.gitea.auto_build)
		if [ "$auto_build" = "1" ]; then
			"$HEXOCTL" build 2>&1 &
		fi

		json_add_boolean "success" 1
		json_add_string "message" "Content synced from Gitea"
		json_add_string "ref" "$ref"

		# Log the event
		logger -t hexojs "Webhook: Synced content from Gitea push to $ref"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Sync failed: $sync_output"
	fi

	json_dump
}

# Configure Gitea webhook
setup_webhook() {
	read input
	json_load "$input"
	json_get_var auto_build auto_build
	json_get_var webhook_secret webhook_secret

	json_init

	[ -n "$auto_build" ] && uci set hexojs.gitea.auto_build="$auto_build"
	[ -n "$webhook_secret" ] && uci set hexojs.gitea.webhook_secret="$webhook_secret"
	uci commit hexojs

	# Generate webhook URL
	local lan_ip=$(uci -q get network.lan.ipaddr || echo "192.168.255.1")
	local webhook_url="http://${lan_ip}/cgi-bin/luci/admin/services/hexojs/webhook"

	json_add_boolean "success" 1
	json_add_string "message" "Webhook configured"
	json_add_string "webhook_url" "$webhook_url"
	json_add_string "hint" "Add this URL to Gitea repository settings > Webhooks"

	json_dump
}

# ============================================
# Instance Health & Pipeline Status
# ============================================

# Get detailed health status for an instance
get_instance_health() {
	read input
	json_load "$input"
	json_get_var instance instance

	json_init

	if [ -z "$instance" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Instance required"
		json_dump
		return
	fi

	local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
	local site_path="$data_path/instances/$instance/site"
	local port=$(uci -q get hexojs.${instance}.port)
	local domain=$(uci -q get hexojs.${instance}.domain)
	local onion=$(uci -q get hexojs.${instance}.onion_address)

	json_add_boolean "success" 1
	json_add_string "instance" "$instance"

	# Overall health score (0-100)
	local health_score=100
	local issues=""

	# Check container
	json_add_object "container"
	if is_running; then
		json_add_string "status" "running"
		json_add_boolean "healthy" 1
	else
		json_add_string "status" "stopped"
		json_add_boolean "healthy" 0
		health_score=$((health_score - 30))
		issues="$issues container_stopped"
	fi
	json_close_object

	# Check site files
	json_add_object "site"
	if [ -d "$site_path" ]; then
		json_add_boolean "exists" 1
		local post_count=$(find "$site_path/source/_posts" -type f -name "*.md" ! -name "index.md" 2>/dev/null | wc -l)
		json_add_int "post_count" "$post_count"

		if [ -d "$site_path/public" ]; then
			json_add_boolean "built" 1
			local public_files=$(find "$site_path/public" -type f 2>/dev/null | wc -l)
			json_add_int "public_files" "$public_files"
		else
			json_add_boolean "built" 0
			health_score=$((health_score - 20))
			issues="$issues not_built"
		fi
	else
		json_add_boolean "exists" 0
		health_score=$((health_score - 40))
		issues="$issues no_site"
	fi
	json_close_object

	# Check server
	json_add_object "server"
	if [ -n "$port" ]; then
		json_add_int "port" "$port"
		# Check if port is listening
		if netstat -tln 2>/dev/null | grep -q ":${port}[[:space:]]"; then
			json_add_boolean "listening" 1
		else
			json_add_boolean "listening" 0
			health_score=$((health_score - 20))
			issues="$issues not_listening"
		fi
	fi
	json_close_object

	# Check HAProxy
	json_add_object "haproxy"
	if [ -n "$domain" ]; then
		json_add_boolean "configured" 1
		json_add_string "domain" "$domain"

		# Check certificate
		local cert_file="/etc/haproxy/certs/${domain}.pem"
		if [ -f "$cert_file" ]; then
			local expiry_epoch=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | sed 's/notAfter=//' | xargs -I{} date -d "{}" +%s 2>/dev/null || echo 0)
			local now_epoch=$(date +%s)
			local days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

			if [ "$days_left" -lt 0 ]; then
				json_add_string "cert_status" "expired"
				health_score=$((health_score - 20))
				issues="$issues cert_expired"
			elif [ "$days_left" -lt 7 ]; then
				json_add_string "cert_status" "critical"
				health_score=$((health_score - 10))
				issues="$issues cert_expiring"
			elif [ "$days_left" -lt 30 ]; then
				json_add_string "cert_status" "warning"
			else
				json_add_string "cert_status" "valid"
			fi
			json_add_int "cert_days_left" "$days_left"
		else
			json_add_string "cert_status" "missing"
			health_score=$((health_score - 10))
			issues="$issues no_cert"
		fi
	else
		json_add_boolean "configured" 0
	fi
	json_close_object

	# Check Tor
	json_add_object "tor"
	if [ -n "$onion" ]; then
		json_add_boolean "configured" 1
		json_add_string "onion_address" "$onion"

		# Check if Tor service file exists
		if [ -f "/var/lib/tor/hidden_service_hexo_${instance}/hostname" ]; then
			json_add_string "status" "active"
		else
			json_add_string "status" "pending"
		fi
	else
		json_add_boolean "configured" 0
	fi
	json_close_object

	# Check Gitea sync
	json_add_object "gitea"
	if [ -d "$site_path/.git" ]; then
		json_add_boolean "configured" 1
		cd "$site_path"
		local behind=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo "0")
		local modified=$(git status --porcelain 2>/dev/null | wc -l)

		if [ "$behind" -gt 0 ]; then
			json_add_string "sync_status" "behind"
			json_add_int "commits_behind" "$behind"
			issues="$issues sync_behind"
		elif [ "$modified" -gt 0 ]; then
			json_add_string "sync_status" "modified"
			json_add_int "local_changes" "$modified"
		else
			json_add_string "sync_status" "synced"
		fi
	else
		json_add_boolean "configured" 0
	fi
	json_close_object

	# Final health score
	[ "$health_score" -lt 0 ] && health_score=0
	json_add_int "health_score" "$health_score"

	if [ "$health_score" -ge 80 ]; then
		json_add_string "health_status" "healthy"
	elif [ "$health_score" -ge 50 ]; then
		json_add_string "health_status" "degraded"
	else
		json_add_string "health_status" "critical"
	fi

	json_add_string "issues" "$issues"

	json_dump
}

# Get pipeline status for all instances
get_pipeline_status() {
	json_init

	local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"

	json_add_array "instances"

	# Iterate through UCI instances
	for instance in $(uci -q show hexojs | grep "=instance" | sed 's/hexojs\.\([^=]*\)=instance/\1/'); do
		local port=$(uci -q get hexojs.${instance}.port)
		local enabled=$(uci -q get hexojs.${instance}.enabled)
		local domain=$(uci -q get hexojs.${instance}.domain)
		local onion=$(uci -q get hexojs.${instance}.onion_address)
		local profile=$(uci -q get hexojs.${instance}.profile)
		local site_path="$data_path/instances/$instance/site"

		json_add_object
		json_add_string "id" "$instance"
		json_add_int "port" "$port"
		json_add_boolean "enabled" "${enabled:-0}"
		json_add_string "profile" "${profile:-default}"

		# Quick status checks
		local status="unknown"
		if [ -d "$site_path" ]; then
			# Check if server is running on this port
			if netstat -tln 2>/dev/null | grep -q ":${port}[[:space:]]"; then
				status="running"
			else
				status="stopped"
			fi
		else
			status="no_site"
		fi
		json_add_string "status" "$status"

		# Endpoints
		json_add_object "endpoints"
		json_add_string "local" "http://localhost:${port}"
		[ -n "$domain" ] && json_add_string "clearnet" "https://${domain}"
		[ -n "$onion" ] && json_add_string "tor" "http://${onion}"
		json_close_object

		json_close_object
	done

	json_close_array

	# Global stats
	json_add_object "stats"
	local total=$(uci -q show hexojs | grep -c "=instance" || echo 0)
	local running=$(netstat -tln 2>/dev/null | grep -c ":400[0-9][[:space:]]" || echo 0)
	json_add_int "total_instances" "$total"
	json_add_int "running_instances" "$running"
	json_close_object

	json_dump
}

# ============================================
# Service Control
# ============================================

service_start() {
	json_init

	/etc/init.d/hexojs start >/dev/null 2>&1 &

	json_add_boolean "success" 1
	json_add_string "message" "Service starting"

	json_dump
}

service_stop() {
	json_init

	/etc/init.d/hexojs stop >/dev/null 2>&1

	json_add_boolean "success" 1
	json_add_string "message" "Service stopped"

	json_dump
}

service_restart() {
	json_init

	/etc/init.d/hexojs restart >/dev/null 2>&1 &

	json_add_boolean "success" 1
	json_add_string "message" "Service restarting"

	json_dump
}

# ============================================
# Main dispatcher
# ============================================

case "$1" in
	list)
		cat << 'EOF'
{
  "status": {},
  "site_stats": {},
  "list_posts": {},
  "get_post": {"slug": "str"},
  "create_post": {"title": "str", "content": "str", "categories": "str", "tags": "str", "excerpt": "str"},
  "update_post": {"slug": "str", "title": "str", "content": "str", "categories": "str", "tags": "str", "excerpt": "str", "cover": "str"},
  "delete_post": {"slug": "str"},
  "publish_post": {"slug": "str"},
  "list_drafts": {},
  "search_posts": {"query": "str", "category": "str", "tag": "str"},
  "list_categories": {},
  "list_tags": {},
  "list_media": {},
  "delete_media": {"path": "str"},
  "list_apps": {},
  "create_app": {"title": "str", "icon": "str", "description": "str", "url": "str", "category": "str", "content": "str"},
  "generate": {},
  "clean": {},
  "deploy": {},
  "deploy_status": {},
  "preview_start": {},
  "preview_status": {},
  "get_config": {},
  "save_config": {"enabled": "bool", "http_port": "int", "title": "str", "subtitle": "str", "author": "str", "language": "str", "url": "str", "deploy_repo": "str", "deploy_branch": "str"},
  "get_theme_config": {},
  "save_theme_config": {"default_mode": "str", "allow_toggle": "bool", "accent_color": "str", "logo_symbol": "str", "logo_text": "str"},
  "list_presets": {},
  "apply_preset": {"preset_id": "str"},
  "service_start": {},
  "service_stop": {},
  "service_restart": {},
  "git_status": {},
  "git_init": {"repo": "str", "branch": "str"},
  "git_clone": {"repo": "str", "branch": "str"},
  "git_pull": {},
  "git_push": {"message": "str", "force": "bool"},
  "git_fetch": {},
  "git_log": {},
  "git_reset": {"hard": "bool"},
  "git_set_credentials": {"name": "str", "email": "str"},
  "git_get_credentials": {},
  "gitea_status": {},
  "gitea_setup": {},
  "gitea_clone": {},
  "gitea_sync": {},
  "gitea_save_config": {"enabled": "bool", "gitea_url": "str", "gitea_user": "str", "gitea_token": "str", "content_repo": "str", "content_branch": "str", "auto_sync": "bool"},
  "publish_to_www": {"path": "str"},
  "get_workflow_status": {},
  "list_profiles": {},
  "apply_profile": {"instance": "str", "profile": "str", "domain": "str", "enable_tor": "bool"},
  "publish_to_haproxy": {"instance": "str", "domain": "str", "acme": "bool"},
  "unpublish_from_haproxy": {"instance": "str"},
  "get_haproxy_status": {"instance": "str"},
  "publish_to_tor": {"instance": "str"},
  "unpublish_from_tor": {"instance": "str"},
  "get_tor_status": {"instance": "str"},
  "get_instance_endpoints": {"instance": "str"},
  "full_publish": {"instance": "str", "rebuild": "bool"},
  "handle_webhook": {"event": "str", "repository": "str", "ref": "str", "secret": "str"},
  "setup_webhook": {"auto_build": "bool", "webhook_secret": "str"},
  "get_instance_health": {"instance": "str"},
  "get_pipeline_status": {}
}
EOF
		;;
	call)
		case "$2" in
			status) get_status ;;
			site_stats) get_site_stats ;;
			list_posts) list_posts ;;
			get_post) get_post ;;
			create_post) create_post ;;
			update_post) update_post ;;
			delete_post) delete_post ;;
			publish_post) publish_post ;;
			list_drafts) list_drafts ;;
			search_posts) search_posts ;;
			list_categories) list_categories ;;
			list_tags) list_tags ;;
			list_media) list_media ;;
			delete_media) delete_media ;;
			list_apps) list_apps ;;
			create_app) create_app ;;
			generate) do_generate ;;
			clean) do_clean ;;
			deploy) do_deploy ;;
			deploy_status) get_deploy_status ;;
			preview_start) preview_start ;;
			preview_status) preview_status ;;
			get_config) get_config ;;
			save_config) save_config ;;
			get_theme_config) get_theme_config ;;
			save_theme_config) save_theme_config ;;
			list_presets) list_presets ;;
			apply_preset) apply_preset ;;
			service_start) service_start ;;
			service_stop) service_stop ;;
			service_restart) service_restart ;;
			git_status) git_status ;;
			git_init) git_init ;;
			git_clone) git_clone ;;
			git_pull) git_pull ;;
			git_push) git_push ;;
			git_fetch) git_fetch ;;
			git_log) git_log ;;
			git_reset) git_reset ;;
			git_set_credentials) git_set_credentials ;;
			git_get_credentials) git_get_credentials ;;
			gitea_status) gitea_status ;;
			gitea_setup) gitea_setup ;;
			gitea_clone) gitea_clone ;;
			gitea_sync) gitea_sync ;;
			gitea_save_config) gitea_save_config ;;
			publish_to_www) publish_to_www ;;
			get_workflow_status) get_workflow_status ;;
			list_profiles) list_profiles ;;
			apply_profile) apply_profile ;;
			publish_to_haproxy) publish_to_haproxy ;;
			unpublish_from_haproxy) unpublish_from_haproxy ;;
			get_haproxy_status) get_haproxy_status ;;
			publish_to_tor) publish_to_tor ;;
			unpublish_from_tor) unpublish_from_tor ;;
			get_tor_status) get_tor_status ;;
			get_instance_endpoints) get_instance_endpoints ;;
			full_publish) full_publish ;;
			handle_webhook) handle_webhook ;;
			setup_webhook) setup_webhook ;;
			get_instance_health) get_instance_health ;;
			get_pipeline_status) get_pipeline_status ;;
			*) echo '{"error": "Unknown method"}' ;;
		esac
		;;
esac
