<?php
/**
 * WooCommerce product categories (product_cat) — list, tree, CRUD helpers for ZERTI inventory.
 */

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Product category taxonomy slug.
 */
function zerti_product_cat_taxonomy(): string
{
    return 'product_cat';
}

/**
 * Whether WooCommerce product categories are available.
 */
function zerti_product_categories_available(): bool
{
    return taxonomy_exists(zerti_product_cat_taxonomy());
}

/**
 * Capability required to manage product categories in ZERTI.
 */
function zerti_product_categories_capability(): string
{
    return 'manage_product_terms';
}

/**
 * Whether the current user may manage product categories.
 */
function zerti_user_can_manage_product_categories(): bool
{
    if (current_user_can(zerti_product_categories_capability())) {
        return true;
    }

    return current_user_can('edit_products');
}

/**
 * WooCommerce category display type choices.
 *
 * @return array<string, string>
 */
function zerti_product_category_display_type_choices(): array
{
    return [
        'default' => __('Default', 'zerti'),
        'products' => __('Products only', 'zerti'),
        'subcategories' => __('Subcategories only', 'zerti'),
        'both' => __('Products and subcategories', 'zerti'),
    ];
}

/**
 * Normalize display_type for storage.
 */
function zerti_product_category_sanitize_display_type(string $value): string
{
    $choices = zerti_product_category_display_type_choices();

    return array_key_exists($value, $choices) ? $value : 'default';
}

/**
 * Serialize one product_cat term for JSON.
 *
 * @return array<string, mixed>|null
 */
function zerti_product_category_to_item(WP_Term $term): ?array
{
    if ($term->taxonomy !== zerti_product_cat_taxonomy()) {
        return null;
    }

    $thumb_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
    $thumb_url = $thumb_id > 0 ? (string) wp_get_attachment_image_url($thumb_id, 'thumbnail') : '';
    $display_type = (string) get_term_meta($term->term_id, 'display_type', true);
    if ($display_type === '') {
        $display_type = 'default';
    }

    return [
        'id' => (int) $term->term_id,
        'name' => $term->name,
        'slug' => $term->slug,
        'parent' => (int) $term->parent,
        'description' => $term->description,
        'count' => (int) $term->count,
        'order' => (int) get_term_meta($term->term_id, 'order', true),
        'thumbnail_id' => $thumb_id,
        'thumbnail_url' => $thumb_url,
        'display_type' => zerti_product_category_sanitize_display_type($display_type),
        'edit_url' => get_edit_term_link($term, zerti_product_cat_taxonomy(), 'product'),
    ];
}

/**
 * All product categories as a flat list (sorted for tree building).
 *
 * @return list<array<string, mixed>>
 */
function zerti_product_categories_flat_list(): array
{
    if (!zerti_product_categories_available()) {
        return [];
    }

    $terms = get_terms([
        'taxonomy' => zerti_product_cat_taxonomy(),
        'hide_empty' => false,
        'orderby' => 'name',
        'order' => 'ASC',
    ]);

    if (!is_array($terms) || is_wp_error($terms)) {
        return [];
    }

    $items = [];
    foreach ($terms as $term) {
        if (!$term instanceof WP_Term) {
            continue;
        }
        $row = zerti_product_category_to_item($term);
        if ($row !== null) {
            $items[] = $row;
        }
    }

    usort(
        $items,
        static function (array $a, array $b): int {
            $oa = (int) ($a['order'] ?? 0);
            $ob = (int) ($b['order'] ?? 0);
            if ($oa !== $ob) {
                return $oa <=> $ob;
            }

            return strcasecmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? ''));
        }
    );

    return $items;
}

/**
 * Build nested children arrays from a flat list.
 *
 * @param list<array<string, mixed>> $flat
 * @return list<array<string, mixed>>
 */
function zerti_product_categories_build_tree(array $flat): array
{
    $by_parent = [];
    foreach ($flat as $row) {
        $parent = (int) ($row['parent'] ?? 0);
        if (!isset($by_parent[$parent])) {
            $by_parent[$parent] = [];
        }
        $by_parent[$parent][] = $row;
    }

    $walk = static function (int $parent_id) use (&$walk, $by_parent): array {
        $nodes = $by_parent[$parent_id] ?? [];
        $out = [];
        foreach ($nodes as $node) {
            $id = (int) ($node['id'] ?? 0);
            $node['children'] = $walk($id);
            $out[] = $node;
        }

        return $out;
    };

    return $walk(0);
}

/**
 * Descendant term IDs (excluding $term_id itself).
 *
 * @return list<int>
 */
function zerti_product_category_descendant_ids(int $term_id): array
{
    if ($term_id < 1 || !zerti_product_categories_available()) {
        return [];
    }

    $children = get_terms([
        'taxonomy' => zerti_product_cat_taxonomy(),
        'hide_empty' => false,
        'parent' => $term_id,
        'fields' => 'ids',
    ]);
    if (!is_array($children) || is_wp_error($children)) {
        return [];
    }

    $ids = [];
    foreach ($children as $child_id) {
        $child_id = (int) $child_id;
        if ($child_id < 1) {
            continue;
        }
        $ids[] = $child_id;
        $ids = array_merge($ids, zerti_product_category_descendant_ids($child_id));
    }

    return $ids;
}

/**
 * Parent choices for a term (excludes self and descendants).
 *
 * @return list<array{id:int,label:string,depth:int}>
 */
function zerti_product_category_parent_options(int $exclude_term_id = 0): array
{
    $flat = zerti_product_categories_flat_list();
    $exclude = [];
    if ($exclude_term_id > 0) {
        $exclude[$exclude_term_id] = true;
        foreach (zerti_product_category_descendant_ids($exclude_term_id) as $desc_id) {
            $exclude[$desc_id] = true;
        }
    }

    $tree = zerti_product_categories_build_tree($flat);
    $options = [
        [
            'id' => 0,
            'label' => __('— No parent (root category) —', 'zerti'),
            'depth' => 0,
        ],
    ];

    $walk = static function (array $nodes, int $depth) use (&$walk, &$options, $exclude): void {
        foreach ($nodes as $node) {
            $id = (int) ($node['id'] ?? 0);
            if ($id < 1 || isset($exclude[$id])) {
                continue;
            }
            $prefix = $depth > 0 ? str_repeat('— ', $depth) : '';
            $options[] = [
                'id' => $id,
                'label' => $prefix . (string) ($node['name'] ?? ''),
                'depth' => $depth,
            ];
            if (!empty($node['children']) && is_array($node['children'])) {
                $walk($node['children'], $depth + 1);
            }
        }
    };

    $walk($tree, 0);

    return $options;
}

/**
 * Validate parent assignment (no self, no cycles).
 */
function zerti_product_category_validate_parent(int $term_id, int $parent_id): bool
{
    if ($parent_id < 1) {
        return true;
    }
    if ($term_id > 0 && $parent_id === $term_id) {
        return false;
    }
    if ($term_id > 0) {
        $blocked = zerti_product_category_descendant_ids($term_id);
        if (in_array($parent_id, $blocked, true)) {
            return false;
        }
    }

    $parent = get_term($parent_id, zerti_product_cat_taxonomy());

    return $parent instanceof WP_Term && !is_wp_error($parent);
}

/**
 * @param array<string, mixed> $source
 * @return array{id:int,name:string,slug:string,parent:int,description:string,display_type:string,thumbnail_id:int,order:int}
 */
function zerti_product_category_payload_from_array(array $source): array
{
    $id = isset($source['id']) ? absint(wp_unslash((string) $source['id'])) : 0;
    $name = isset($source['name']) ? sanitize_text_field(wp_unslash((string) $source['name'])) : '';
    $slug = isset($source['slug']) ? sanitize_title(wp_unslash((string) $source['slug'])) : '';
    $parent = isset($source['parent']) ? absint(wp_unslash((string) $source['parent'])) : 0;
    $description = isset($source['description']) ? wp_kses_post(wp_unslash((string) $source['description'])) : '';
    $display_type = isset($source['display_type'])
        ? zerti_product_category_sanitize_display_type(sanitize_key(wp_unslash((string) $source['display_type'])))
        : 'default';
    $thumbnail_id = isset($source['thumbnail_id']) ? absint(wp_unslash((string) $source['thumbnail_id'])) : 0;
    $order = isset($source['order']) ? (int) wp_unslash((string) $source['order']) : 0;

    return [
        'id' => $id,
        'name' => $name,
        'slug' => $slug,
        'parent' => $parent,
        'description' => $description,
        'display_type' => $display_type,
        'thumbnail_id' => $thumbnail_id,
        'order' => $order,
    ];
}

/**
 * Read category payload from POST/JSON request.
 *
 * @return array{id:int,name:string,slug:string,parent:int,description:string,display_type:string,thumbnail_id:int,order:int}
 */
/**
 * Create or update a product category.
 *
 * @param array{id:int,name:string,slug:string,parent:int,description:string,display_type:string,thumbnail_id:int,order:int} $payload
 * @return array{ok:bool,item?:array<string,mixed>,message?:string,code?:string}
 */
function zerti_product_category_save(array $payload): array
{
    if (!zerti_product_categories_available()) {
        return ['ok' => false, 'message' => __('Taxonomy missing', 'zerti'), 'code' => 'taxonomy_missing'];
    }

    $name = trim($payload['name'] ?? '');
    if ($name === '') {
        return ['ok' => false, 'message' => __('Name is required', 'zerti'), 'code' => 'name_required'];
    }

    $id = (int) ($payload['id'] ?? 0);
    $parent = (int) ($payload['parent'] ?? 0);
    if (!zerti_product_category_validate_parent($id, $parent)) {
        return ['ok' => false, 'message' => __('Invalid parent', 'zerti'), 'code' => 'invalid_parent'];
    }

    $slug = trim((string) ($payload['slug'] ?? ''));
    if ($slug === '') {
        $slug = sanitize_title($name);
    }

    $args = [
        'description' => (string) ($payload['description'] ?? ''),
        'parent' => $parent,
        'slug' => $slug,
    ];

    if ($id > 0) {
        $existing = get_term($id, zerti_product_cat_taxonomy());
        if (!$existing instanceof WP_Term || is_wp_error($existing)) {
            return ['ok' => false, 'message' => __('Not found', 'zerti'), 'code' => 'not_found'];
        }

        $result = wp_update_term($id, zerti_product_cat_taxonomy(), array_merge($args, ['name' => $name]));
    } else {
        $result = wp_insert_term($name, zerti_product_cat_taxonomy(), $args);
    }

    if (is_wp_error($result)) {
        return [
            'ok' => false,
            'message' => $result->get_error_message(),
            'code' => (string) $result->get_error_code(),
        ];
    }

    $term_id = $id > 0 ? $id : (int) ($result['term_id'] ?? 0);
    if ($term_id < 1) {
        return ['ok' => false, 'message' => __('Save failed', 'zerti'), 'code' => 'save_failed'];
    }

    $display_type = zerti_product_category_sanitize_display_type((string) ($payload['display_type'] ?? 'default'));
    update_term_meta($term_id, 'display_type', $display_type);

    $order = (int) ($payload['order'] ?? 0);
    update_term_meta($term_id, 'order', $order);

    $thumb_id = (int) ($payload['thumbnail_id'] ?? 0);
    if ($thumb_id > 0 && !wp_attachment_is_image($thumb_id)) {
        $thumb_id = 0;
    }
    if ($thumb_id > 0) {
        update_term_meta($term_id, 'thumbnail_id', $thumb_id);
    } else {
        delete_term_meta($term_id, 'thumbnail_id');
    }

    $term = get_term($term_id, zerti_product_cat_taxonomy());
    if (!$term instanceof WP_Term || is_wp_error($term)) {
        return ['ok' => false, 'message' => __('Save failed', 'zerti'), 'code' => 'save_failed'];
    }

    $item = zerti_product_category_to_item($term);

    return $item !== null
        ? ['ok' => true, 'item' => $item]
        : ['ok' => false, 'message' => __('Save failed', 'zerti'), 'code' => 'save_failed'];
}

/**
 * Delete a product category.
 *
 * @return array{ok:bool,message?:string,code?:string}
 */
function zerti_product_category_delete(int $term_id): array
{
    if ($term_id < 1 || !zerti_product_categories_available()) {
        return ['ok' => false, 'message' => __('Not found', 'zerti'), 'code' => 'not_found'];
    }

    $term = get_term($term_id, zerti_product_cat_taxonomy());
    if (!$term instanceof WP_Term || is_wp_error($term)) {
        return ['ok' => false, 'message' => __('Not found', 'zerti'), 'code' => 'not_found'];
    }

    $result = wp_delete_term($term_id, zerti_product_cat_taxonomy());
    if (is_wp_error($result)) {
        return [
            'ok' => false,
            'message' => $result->get_error_message(),
            'code' => (string) $result->get_error_code(),
        ];
    }
    if ($result === false) {
        return ['ok' => false, 'message' => __('Delete failed', 'zerti'), 'code' => 'delete_failed'];
    }

    return ['ok' => true];
}

/**
 * Reorder siblings: array of {id, order} (and optional parent).
 *
 * @param list<array{id:int,order:int,parent?:int}> $rows
 * @return array{ok:bool,message?:string}
 */
function zerti_product_categories_apply_reorder(array $rows): array
{
    if (!zerti_product_categories_available()) {
        return ['ok' => false, 'message' => __('Taxonomy missing', 'zerti')];
    }

    foreach ($rows as $row) {
        if (!is_array($row)) {
            continue;
        }
        $id = isset($row['id']) ? absint($row['id']) : 0;
        if ($id < 1) {
            continue;
        }
        $order = isset($row['order']) ? (int) $row['order'] : 0;
        update_term_meta($id, 'order', $order);

        if (array_key_exists('parent', $row)) {
            $parent = absint($row['parent']);
            if (zerti_product_category_validate_parent($id, $parent)) {
                wp_update_term($id, zerti_product_cat_taxonomy(), ['parent' => $parent]);
            }
        }
    }

    return ['ok' => true];
}

/**
 * Human-readable error for AJAX responses.
 */
function zerti_product_category_error_message(string $code, string $fallback = ''): string
{
    $map = [
        'name_required' => __('Category name is required.', 'zerti'),
        'invalid_parent' => __('Invalid parent category (cannot be the category itself or its descendant).', 'zerti'),
        'not_found' => __('Category not found.', 'zerti'),
        'taxonomy_missing' => __('WooCommerce product category is not available.', 'zerti'),
        'delete_failed' => __('Could not delete category.', 'zerti'),
        'save_failed' => __('Could not save category.', 'zerti'),
        'term_exists' => __('Duplicate category name or slug.', 'zerti'),
    ];

    if (isset($map[$code])) {
        return $map[$code];
    }

    return $fallback !== '' ? $fallback : __('An error occurred.', 'zerti');
}
