package sync

import (
	"fmt"
	"log"

	"golang.org/x/net/html"
)

type KEPUBCFIConverter struct {
	epubConverter  *CFIConverter
	kepubConverter *CFIConverter
}

type KEPUBConversionResult struct {
	CFI               string
	ExtractedContext  string
	Percentage        float64
	Precision         string
}

func NewKEPUBCFIConverter(epubPath, kepubPath string) *KEPUBCFIConverter {
	return &KEPUBCFIConverter{
		epubConverter:  NewCFIConverter(epubPath),
		kepubConverter: NewCFIConverter(kepubPath),
	}
}

func (k *KEPUBCFIConverter) ConvertKEPUBCFIToStandard(kepubCFI string, percentage float64, contextText string) (*KEPUBConversionResult, error) {
	if !IsStandardEPUBCFI(kepubCFI) {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	spineIndex, steps, err := parseEPUBCFI(kepubCFI)
	if err != nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	kepubDoc, _, docErr := k.kepubConverter.getContentDoc(spineIndex + 1)
	if docErr != nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	kepubTextNode, kepubOffset, resolveErr := resolveCFIToNode(kepubDoc, steps)
	if resolveErr != nil {
		return k.kepubToStandardByPercentage(spineIndex, percentage)
	}

	var searchText string
	if contextText != "" {
		searchText = normalizeWhitespace(contextText)
	} else if kepubTextNode != nil && kepubTextNode.Type == html.TextNode {
		searchText = extractSurroundingText(kepubTextNode, kepubOffset, 80)
	}

	if searchText != "" {
		epubDoc, _, docErr := k.epubConverter.getContentDoc(spineIndex + 1)
		if docErr == nil {
			epubBody := findBody(epubDoc)
			if epubBody != nil {
				matchNode, matchOffset := findTextInNode(epubBody, searchText)
				if matchNode != nil {
					cfi, buildErr := buildCFI(spineIndex, matchNode, matchOffset)
					if buildErr == nil && cfi != "" {
						return &KEPUBConversionResult{
							CFI:              cfi,
							ExtractedContext: searchText,
							Percentage:       percentage,
							Precision:        "exact",
						}, nil
					}
				}
			}
		}
	}

	result, _ := k.kepubToStandardByPercentage(spineIndex, percentage)
	result.ExtractedContext = searchText
	return result, nil
}

func (k *KEPUBCFIConverter) ConvertStandardCFIToKEPUB(standardCFI string, percentage float64, contextText string) (*KEPUBConversionResult, error) {
	if !IsStandardEPUBCFI(standardCFI) {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	spineIndex, steps, err := parseEPUBCFI(standardCFI)
	if err != nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	epubDoc, _, docErr := k.epubConverter.getContentDoc(spineIndex + 1)
	if docErr != nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	epubTextNode, epubOffset, resolveErr := resolveCFIToNode(epubDoc, steps)
	if resolveErr != nil {
		return k.standardToKEPUBByPercentage(spineIndex, percentage)
	}

	var searchText string
	if contextText != "" {
		searchText = normalizeWhitespace(contextText)
	} else if epubTextNode != nil && epubTextNode.Type == html.TextNode {
		searchText = extractSurroundingText(epubTextNode, epubOffset, 80)
	}

	if searchText != "" {
		kepubDoc, _, docErr := k.kepubConverter.getContentDoc(spineIndex + 1)
		if docErr == nil {
			kepubBody := findBody(kepubDoc)
			if kepubBody != nil {
				matchNode, matchOffset := findTextInNode(kepubBody, searchText)
				if matchNode != nil {
					cfi, buildErr := buildCFI(spineIndex, matchNode, matchOffset)
					if buildErr == nil && cfi != "" {
						return &KEPUBConversionResult{
							CFI:              cfi,
							ExtractedContext: searchText,
							Percentage:       percentage,
							Precision:        "exact",
						}, nil
					}
				}
			}
		}
	}

	result, _ := k.standardToKEPUBByPercentage(spineIndex, percentage)
	result.ExtractedContext = searchText
	return result, nil
}

func (k *KEPUBCFIConverter) kepubToStandardByPercentage(spineIndex int, percentage float64) (*KEPUBConversionResult, error) {
	if percentage <= 0 {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	epubDoc, _, docErr := k.epubConverter.getContentDoc(spineIndex + 1)
	if docErr != nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	epubBody := findBody(epubDoc)
	if epubBody == nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	totalChars := countTextChars(epubBody)
	if totalChars <= 0 {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	targetOffset := int(float64(totalChars) * percentage)
	targetNode, foundOffset := findNodeAtCharOffset(epubBody, targetOffset)
	if targetNode == nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	charInNode := targetOffset - foundOffset
	cfi, err := buildCFI(spineIndex, targetNode, charInNode)
	if err != nil || cfi == "" {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	return &KEPUBConversionResult{
		CFI:        cfi,
		Percentage: percentage,
		Precision:  "percentage",
	}, nil
}

func (k *KEPUBCFIConverter) standardToKEPUBByPercentage(spineIndex int, percentage float64) (*KEPUBConversionResult, error) {
	if percentage <= 0 {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	kepubDoc, _, docErr := k.kepubConverter.getContentDoc(spineIndex + 1)
	if docErr != nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	kepubBody := findBody(kepubDoc)
	if kepubBody == nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	totalChars := countTextChars(kepubBody)
	if totalChars <= 0 {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	targetOffset := int(float64(totalChars) * percentage)
	targetNode, foundOffset := findNodeAtCharOffset(kepubBody, targetOffset)
	if targetNode == nil {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	charInNode := targetOffset - foundOffset
	cfi, err := buildCFI(spineIndex, targetNode, charInNode)
	if err != nil || cfi == "" {
		return &KEPUBConversionResult{Percentage: percentage, Precision: "percentage"}, nil
	}

	return &KEPUBConversionResult{
		CFI:        cfi,
		Percentage: percentage,
		Precision:  "percentage",
	}, nil
}

func extractSurroundingText(textNode *html.Node, offset int, window int) string {
	if textNode == nil || textNode.Type != html.TextNode {
		return ""
	}

	block := findBlockParent(textNode)
	if block == nil {
		text := textNode.Data
		runes := []rune(text)
		start := offset - window
		if start < 0 {
			start = 0
		}
		end := offset + window
		if end > len(runes) {
			end = len(runes)
		}
		if start >= end {
			return normalizeWhitespace(text)
		}
		return normalizeWhitespace(string(runes[start:end]))
	}

	segments := collectInlineText(block)

	globalOffset := 0
	for _, seg := range segments {
		if seg.node == textNode {
			globalOffset += offset
			break
		}
		globalOffset += len(seg.runes)
	}

	var allRunes []rune
	for _, seg := range segments {
		allRunes = append(allRunes, seg.runes...)
	}

	start := globalOffset - window
	if start < 0 {
		start = 0
	}
	end := globalOffset + window
	if end > len(allRunes) {
		end = len(allRunes)
	}

	if start >= end {
		return normalizeWhitespace(string(allRunes))
	}

	return normalizeWhitespace(string(allRunes[start:end]))
}

func (k *KEPUBCFIConverter) ComputeKEPUBPercentage(kepubCFI string) (float64, error) {
	if !IsStandardEPUBCFI(kepubCFI) {
		return -1, fmt.Errorf("not a standard epubcfi")
	}

	spineIndex, steps, err := parseEPUBCFI(kepubCFI)
	if err != nil {
		return -1, err
	}

	kepubDoc, _, docErr := k.kepubConverter.getContentDoc(spineIndex + 1)
	if docErr != nil {
		return -1, docErr
	}

	kepubBody := findBody(kepubDoc)
	if kepubBody == nil {
		return -1, fmt.Errorf("no body in kepub doc")
	}

	textNode, textOffset, resolveErr := resolveCFIToNode(kepubBody, steps)
	if resolveErr != nil {
		return -1, resolveErr
	}

	charOffset := countTextCharsBefore(textNode) + textOffset

	total := 0
	kepubSpine, spineErr := k.kepubConverter.loadSpine()
	if spineErr != nil {
		return -1, spineErr
	}

	for i := range kepubSpine.items {
		doc, _, dErr := k.kepubConverter.getContentDoc(i + 1)
		if dErr != nil {
			continue
		}
		b := findBody(doc)
		if b != nil {
			total += countTextChars(b)
		}
	}

	if total <= 0 {
		return -1, fmt.Errorf("no text in kepub")
	}

	return float64(charOffset) / float64(total), nil
}

func init() {
	_ = log.Printf
}
