package adapters

import "core:fmt"
import "core:os"
import filepath "core:path/filepath"
import "core:strings"
import "../core"
import "../shared"

Export_Options :: struct {
	format: core.Export_Format,
	page_size: core.Page_Size_Name,
	dpi: int,
	quality: int,
}

Ordered_Panel :: struct {
	page_number: int,
	panel_number: int,
	panel_id: string,
}

Panel_Render_Entry :: struct {
	path: string,
	px: int,
	py: int,
	pw: int,
	ph: int,
}

collect_ordered_panels :: proc(layouts: []core.Page_Layout) -> []Ordered_Panel {
	panels: [dynamic]Ordered_Panel
	for page in layouts {
		for p in page.panels {
			append(&panels, Ordered_Panel{
				page_number = page.page_number,
				panel_number = p.panel_number,
				panel_id = p.panel_id,
			})
		}
	}
	return panels[:]
}

path_join2 :: proc(a, b: string) -> (string, shared.App_Error) {
	parts := [2]string{a, b}
	out, err := filepath.join(parts[:], context.allocator)
	if err != nil {
		msg := fmt.aprintf("path join failed: %v", err)
		err_out := shared.new_error(.Export, msg, true)
		delete(msg)
		return "", err_out
	}
	return out, shared.ok()
}

run_command :: proc(args: []string) -> shared.App_Error {
	desc := os.Process_Desc{command = args}
	state, _, stderr, err := os.process_exec(desc, context.temp_allocator)
	if err != nil {
		msg := fmt.aprintf("command failed to execute: %v", err)
		err_out := shared.new_error(.Export, msg, true)
		delete(msg)
		return err_out
	}
	if !state.exited || state.exit_code != 0 {
		err_text := string(stderr)
		msg := fmt.aprintf("command failed: %s", err_text)
		err_out := shared.new_error(.Export, msg, true)
		delete(msg)
		return err_out
	}
	return shared.ok()
}

file_ext_from_url :: proc(url: string) -> string {
	base := url
	if q := strings.index(base, "?"); q >= 0 {
		base = base[:q]
	}
	ext := filepath.ext(base)
	if len(ext) == 0 {
		return ".png"
	}
	return ext
}

download_or_copy_panel :: proc(url: string, out_path: string) -> shared.App_Error {
	if strings.has_prefix(url, "file://") {
		src_path := url[len("file://"):]
		if cerr := os.copy_file(out_path, src_path); cerr != nil {
			msg := fmt.aprintf("failed to copy local panel image: %v", cerr)
			err_out := shared.new_error(.Export, msg, true)
			delete(msg)
			return err_out
		}
	} else {
		cmd := [6]string{"curl", "-L", "-sS", "-o", out_path, url}
		if cerr := run_command(cmd[:]); !shared.is_ok(cerr) {
			msg := fmt.aprintf("failed to download panel image: %s", cerr.message)
			err_out := shared.new_error(.Export, msg, true)
			delete(msg)
			return err_out
		}
	}
	return shared.ok()
}

get_page_dimensions :: proc(page_size: core.Page_Size_Name) -> (int, int) {
	switch page_size {
	case .A4: return 2480, 3508
	case .Letter: return 2550, 3300
	case .Manga: return 2158, 3035
	case .Webtoon: return 800, 1280
	case .Square: return 1080, 1080
	}
	return 2480, 3508
}

render_page_to_image :: proc(
	layout: core.Page_Layout,
	panel_images: map[string]core.Panel_Image,
	temp_dir: string,
	page_idx: int,
	page_size: core.Page_Size_Name,
) -> (string, shared.App_Error) {
	page_w, page_h := get_page_dimensions(page_size)
	out_path := fmt.aprintf("%s/page_%03d.png", temp_dir, page_idx+1)
	defer delete(out_path)

	// Collect panel image paths and their positions
	entries: [dynamic]Panel_Render_Entry
	defer delete(entries)

	for p in layout.panels {
		img, has := panel_images[p.panel_id]
		if !has || len(img.url) == 0 {
			continue
		}

		// Download/copy panel to temp
		ext := file_ext_from_url(img.url)
		panel_tmp := fmt.aprintf("%s/panel_%s%s", temp_dir, p.panel_id, ext)
		defer delete(panel_tmp)
		if derr := download_or_copy_panel(img.url, panel_tmp); !shared.is_ok(derr) {
			return "", derr
		}

		// Calculate pixel position from layout cell fractions
		gutter := 4
		px := int(p.layout_cell.x * f32(page_w)) + gutter
		py := int(p.layout_cell.y * f32(page_h)) + gutter
		pw := int(p.layout_cell.w * f32(page_w)) - gutter * 2
		ph := int(p.layout_cell.h * f32(page_h)) - gutter * 2

		if pw <= 0 || ph <= 0 {
			continue
		}

		// Resize panel to fit cell
		resized := fmt.aprintf("%s/panel_%s_resized.png", temp_dir, p.panel_id)
		defer delete(resized)
		resize_cmd: [dynamic]string
		append(&resize_cmd, "magick")
		append(&resize_cmd, panel_tmp)
		append(&resize_cmd, "-resize")
		append(&resize_cmd, fmt.aprintf("%dx%d!", pw, ph))
		append(&resize_cmd, resized)
		defer delete(resize_cmd)
		if rerr := run_command(resize_cmd[:]); !shared.is_ok(rerr) {
			msg := fmt.aprintf("failed to resize panel %s: %s", p.panel_id, rerr.message)
			err_out := shared.new_error(.Export, msg, true)
			delete(msg)
			return "", err_out
		}

		append(&entries, Panel_Render_Entry{path = resized, px = px, py = py, pw = pw, ph = ph})
	}

	if len(entries) == 0 {
		// No panels to render, just create blank page
		blank_cmd: [dynamic]string
		append(&blank_cmd, "magick")
		append(&blank_cmd, "-size")
		append(&blank_cmd, fmt.aprintf("%dx%d", page_w, page_h))
		append(&blank_cmd, "xc:white")
		append(&blank_cmd, out_path)
		defer delete(blank_cmd)
		if err := run_command(blank_cmd[:]); !shared.is_ok(err) {
			msg := fmt.aprintf("failed to create blank page: %s", err.message)
			err_out := shared.new_error(.Export, msg, true)
			delete(msg)
			return "", err_out
		}
		result := strings.clone(out_path)
		return result, shared.ok()
	}

	// Build composite command: background + overlaid panels
	composite_cmd: [dynamic]string
	defer delete(composite_cmd)
	append(&composite_cmd, "magick")
	append(&composite_cmd, "-size")
	append(&composite_cmd, fmt.aprintf("%dx%d", page_w, page_h))
	append(&composite_cmd, "xc:white")

	for e in entries {
		append(&composite_cmd, e.path)
		append(&composite_cmd, "-geometry")
		append(&composite_cmd, fmt.aprintf("+%d+%d", e.px, e.py))
		append(&composite_cmd, "-composite")
	}

	append(&composite_cmd, out_path)

	if err := run_command(composite_cmd[:]); !shared.is_ok(err) {
		msg := fmt.aprintf("failed to render page %d: %s", page_idx+1, err.message)
		err_out := shared.new_error(.Export, msg, true)
		delete(msg)
		return "", err_out
	}

	result := strings.clone(out_path)
	return result, shared.ok()
}

stage_panel_images :: proc(temp_dir: string, ordered: []Ordered_Panel, panel_images: map[string]core.Panel_Image) -> shared.App_Error {
	staged_count := 0
	for p, idx in ordered {
		img := panel_images[p.panel_id]
		if len(img.url) == 0 {
			continue
		}

		ext := file_ext_from_url(img.url)
		filename := fmt.aprintf("%03d_page%03d_panel%03d%s", idx+1, p.page_number, p.panel_number, ext)
		out_path, jerr := path_join2(temp_dir, filename)
		delete(filename)
		if !shared.is_ok(jerr) {
			return jerr
		}

		if derr := download_or_copy_panel(img.url, out_path); !shared.is_ok(derr) {
			delete(out_path)
			return derr
		}

		delete(out_path)
		staged_count += 1
	}

	if staged_count == 0 {
		return shared.new_error(.Export, "no panel images available for export", false)
	}
	return shared.ok()
}

write_comic_info_xml :: proc(temp_dir: string, page_count: int) -> shared.App_Error {
	comic_info := fmt.aprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ComicInfo>\n  <Title>Generated Comic</Title>\n  <PageCount>%d</PageCount>\n</ComicInfo>\n", page_count)
	defer delete(comic_info)
	path, jerr := path_join2(temp_dir, "ComicInfo.xml")
	if !shared.is_ok(jerr) {
		return jerr
	}
	defer delete(path)
	if err := os.write_entire_file(path, comic_info); err != nil {
		msg := fmt.aprintf("failed to write ComicInfo.xml: %v", err)
		err_out := shared.new_error(.Export, msg, true)
		delete(msg)
		return err_out
	}
	return shared.ok()
}

zip_directory_with_python :: proc(output_path, source_dir: string, include_comic_info: bool) -> shared.App_Error {
	script := "import os,sys,zipfile\nout=sys.argv[1]; src=sys.argv[2]; inc=sys.argv[3]=='1'\nwith zipfile.ZipFile(out,'w',zipfile.ZIP_DEFLATED) as z:\n  for n in sorted(os.listdir(src)):\n    if (not inc) and n=='ComicInfo.xml':\n      continue\n    p=os.path.join(src,n)\n    if os.path.isfile(p):\n      z.write(p,arcname=n)"
	inc := "0"
	if include_comic_info {
		inc = "1"
	}
	cmd := [6]string{"python3", "-c", script, output_path, source_dir, inc}
	return run_command(cmd[:])
}

ensure_export_output_parent_dir :: proc(output_path: string) -> shared.App_Error {
	dir, _ := filepath.split(output_path)
	if len(dir) == 0 {
		return shared.ok()
	}
	if err := os.mkdir_all(dir); err != nil && err != .Exist {
		msg := fmt.aprintf("failed to create export output directory: %v", err)
		err_out := shared.new_error(.Export, msg, true)
		delete(msg)
		return err_out
	}
	return shared.ok()
}

export_comic :: proc(output_path: string, layouts: []core.Page_Layout, panel_images: map[string]core.Panel_Image, opts: Export_Options) -> shared.App_Error {
	if len(output_path) == 0 {
		return shared.new_error(.Export, "output path is empty", false)
	}
	if len(layouts) == 0 {
		return shared.new_error(.Export, "no layouts available for export", false)
	}
	if derr := ensure_export_output_parent_dir(output_path); !shared.is_ok(derr) {
		return derr
	}

	// Check we have at least one panel image
	has_images := false
	for _, img in panel_images {
		if len(img.url) > 0 {
			has_images = true
			break
		}
	}
	if !has_images {
		return shared.new_error(.Export, "no panel images available for export", false)
	}

	switch opts.format {
	case .PDF:
		// Render each page to image, then combine into PDF
		temp_dir, terr := os.make_directory_temp("", "comic-pdf-*", context.temp_allocator)
		if terr != nil {
			msg := fmt.aprintf("failed to create temp dir: %v", terr)
			err_out := shared.new_error(.Export, msg, true)
			delete(msg)
			return err_out
		}
		defer os.remove_all(temp_dir)

		page_paths: [dynamic]string
		defer delete(page_paths)

		for layout, i in layouts {
			page_path, rerr := render_page_to_image(layout, panel_images, temp_dir, i, opts.page_size)
			if !shared.is_ok(rerr) {
				return rerr
			}
			append(&page_paths, page_path)
		}

		// Combine page images into PDF using ImageMagick
		cmd: [dynamic]string
		defer delete(cmd)
		append(&cmd, "magick")
		for pp in page_paths {
			append(&cmd, pp)
		}
		append(&cmd, output_path)

		if err := run_command(cmd[:]); !shared.is_ok(err) {
			msg := fmt.aprintf("failed to create PDF: %s", err.message)
			err_out := shared.new_error(.Export, msg, true)
			delete(msg)
			return err_out
		}
		return shared.ok()

	case .CBZ, .PNG:
		temp_dir, terr := os.make_directory_temp("", "comic-export-*", context.temp_allocator)
		if terr != nil {
			msg := fmt.aprintf("failed to create temp dir: %v", terr)
			err_out := shared.new_error(.Export, msg, true)
			delete(msg)
			return err_out
		}
		defer os.remove_all(temp_dir)

		// Render each page to a composed image
		for layout, i in layouts {
			page_path, rerr := render_page_to_image(layout, panel_images, temp_dir, i, opts.page_size)
			if !shared.is_ok(rerr) {
				return rerr
			}
			defer delete(page_path)

			// Rename to page_XXX format
			ext := ".png"
			if opts.format == .CBZ {
				ext = ".jpg"
			}
			dest := fmt.aprintf("%s/page_%03d%s", temp_dir, int(i)+1, ext)
			defer delete(dest)

			// Convert to appropriate format if needed
			if opts.format == .CBZ {
				// Convert PNG to JPEG for CBZ
				quality_str := fmt.aprintf("%d", opts.quality)
				convert_cmd: [dynamic]string
				append(&convert_cmd, "magick")
				append(&convert_cmd, page_path)
				append(&convert_cmd, "-quality")
				append(&convert_cmd, quality_str)
				append(&convert_cmd, dest)
				defer delete(convert_cmd)
				if cerr := run_command(convert_cmd[:]); !shared.is_ok(cerr) {
					return cerr
				}
			} else {
				// Just rename for PNG
				if rerr := os.rename(page_path, dest); rerr != nil {
					msg := fmt.aprintf("failed to rename page image: %v", rerr)
					err_out := shared.new_error(.Export, msg, true)
					delete(msg)
					return err_out
				}
			}
		}

		include_comic_info := opts.format == .CBZ
		if include_comic_info {
			if ierr := write_comic_info_xml(temp_dir, len(layouts)); !shared.is_ok(ierr) {
				return ierr
			}
		}

		if zerr := zip_directory_with_python(output_path, temp_dir, include_comic_info); !shared.is_ok(zerr) {
			return zerr
		}
		return shared.ok()
	}

	return shared.new_error(.Export, "unknown export format", false)
}

export_comic_stub :: proc(output_path: string, layouts: []core.Page_Layout, panel_images: map[string]core.Panel_Image, opts: Export_Options) -> shared.App_Error {
	return export_comic(output_path, layouts, panel_images, opts)
}
