/*
 * Copyright 2025 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package generic

import (
	"os"
	"path/filepath"

	"github.com/xuri/excelize/v2"
	"golang.org/x/sync/errgroup"
)

type PreviewFile struct {
	FilePath           string               `json:"file_path,omitempty" xml:"file_path"`
	SingleFilePreviews []*SingleFilePreview `json:"single_file_previews,omitempty" xml:"single_file_previews>single_file_preview"`
}

type SingleFilePreview struct {
	SheetName   string         `json:"sheet_name,omitempty" xml:"sheet_name"`
	Header      []*ExcelCell   `json:"header" xml:"header"`
	Content     [][]*ExcelCell `json:"content,omitempty" xml:"content"`
	MergedCells []*ExcelCell   `json:"merged_cells,omitempty" xml:"merged_cells>merged_cell"`
}

type ExcelCell struct {
	Address string `json:"address,omitempty" xml:"address"` // B1:D1 表示 B1 到 D1 范围的单元格, B1 表示单个单元格
	Value   string `json:"value,omitempty" xml:"value"`     // 单元格的值
}

func PreviewPath(path string) ([]*PreviewFile, error) {
	filePaths, err := getAllFiles(path)
	if err != nil {
		return nil, err
	}
	if len(filePaths) == 0 {
		return nil, nil
	}
	resp := make([]*PreviewFile, len(filePaths))
	eg := errgroup.Group{}
	eg.SetLimit(10)
	for i := range filePaths {
		idx := i
		fp := filePaths[idx]
		eg.Go(func() error {
			var (
				pf *PreviewFile
				e  error
			)
			if ext := filepath.Ext(fp); ext != ".xlsx" { // .xls not support
				pf = &PreviewFile{FilePath: fp}
			} else {
				pf, e = previewExcelDocument(path)
			}
			if e != nil {
				return e
			}
			resp[idx] = pf
			return nil
		})
	}
	if err := eg.Wait(); err != nil {
		return nil, err
	}
	return resp, nil
}

func getAllFiles(path string) ([]string, error) {
	info, err := os.Stat(path)
	if err != nil {
		return nil, err
	}

	if !info.IsDir() {
		if isHiddenFile(info.Name()) {
			return nil, nil
		}
		return []string{path}, nil
	}

	var files []string
	if err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if isHiddenFile(info.Name()) {
			if info.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}
		if !info.IsDir() {
			files = append(files, path)
		}
		return nil
	}); err != nil {
		return nil, err
	}

	return files, nil
}

func previewExcelDocument(filePath string) (*PreviewFile, error) {
	f, err := excelize.OpenFile(filePath)
	if err != nil {
		return nil, err
	}

	defer f.Close()

	pf := &PreviewFile{
		FilePath:           filePath,
		SingleFilePreviews: nil,
	}
	for _, sheetName := range f.GetSheetList() {
		sfp, err := parseSheet(f, sheetName)
		if err != nil {
			return nil, err
		}

		pf.SingleFilePreviews = append(pf.SingleFilePreviews, sfp)
	}

	return pf, nil
}

func parseSheet(f *excelize.File, sheetName string) (*SingleFilePreview, error) {
	preview := &SingleFilePreview{
		SheetName:   sheetName,
		Header:      make([]*ExcelCell, 0),
		Content:     make([][]*ExcelCell, 0),
		MergedCells: make([]*ExcelCell, 0),
	}
	mcs := make([][][]int, 20)
	mergedCells, err := f.GetMergeCells(sheetName)
	if err != nil {
		return nil, err
	}
	for _, cell := range mergedCells {
		lcol, lrow, err := excelize.CellNameToCoordinates(cell.GetStartAxis())
		if err != nil {
			return nil, err
		}
		if lrow >= 20 {
			continue
		}

		rcol, rrow, err := excelize.CellNameToCoordinates(cell.GetEndAxis())
		if err != nil {
			return nil, err
		}
		preview.MergedCells = append(preview.MergedCells, &ExcelCell{
			Address: cell[0],
			Value:   cell.GetCellValue(),
		})
		mcs[lrow] = append(mcs[lrow], []int{lcol, lrow, rcol, rrow})
	}

	rowIter, err := f.Rows(sheetName)
	if err != nil {
		return nil, err
	}

	i := 0
	for rowIter.Next() && i < 20 {
		values, err := rowIter.Columns()
		if err != nil {
			return nil, err
		}
		var rowValues []*ExcelCell
		for j, val := range values {
			col, row := j+1, i+1
			skip := false
			if r := mcs[i]; len(r) > 0 {
				for _, coverage := range r {
					if col >= coverage[0] && row >= coverage[1] && col <= coverage[2] && row <= coverage[3] {
						skip = true
						break
					}
				}
			}
			if skip {
				continue
			}
			addr, err := excelize.CoordinatesToCellName(col, row)
			if err != nil {
				return nil, err
			}
			rowValues = append(rowValues, &ExcelCell{Address: addr, Value: val})
		}
		if i == 0 {
			preview.Header = rowValues
		} else {
			preview.Content = append(preview.Content, rowValues)
		}
		i++
	}

	return preview, nil
}

func isHiddenFile(name string) bool {
	return len(name) > 0 && name[0] == '.'
}
