<?php
/**
 * Import preview helpers (mapping stage).
 *
 * @package PrimeLink
 */

namespace PrimeLink\Modules\Imports\Service;

use PrimeLink\Modules\Imports\Parsers\ParserFactory;

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

final class ImportPreview {

    /**
     * Build mapping preview payload for Step 5.
     *
     * Expected $file format:
     * [
     *   'path' => '/abs/path/to/file.csv',
     *   'type' => 'csv'|'xml'|'xlsx'|'json',
     *   ...
     * ]
     *
     * @param array $file File descriptor from draft.
     * @param array $args {index:int, xpath:string, max_rows:int}
     * @return array
     */
    public static function get_mapping_preview( array $file, array $args = array() ) {

        $defaults = array(
            'index'    => 0,
            'xpath'    => '',
            'max_rows' => 0, // 0 = unlimited for preview loaders
            'filters'  => array(),
        );
        $args = wp_parse_args( $args, $defaults );

        $index = max( 0, (int) $args['index'] );
        $xpath = (string) $args['xpath'];
        $filters = is_array( $args['filters'] ) ? $args['filters'] : array();

        if ( empty( $file['path'] ) || empty( $file['type'] ) ) {
            return array(
                'success' => false,
                'message' => 'Invalid file descriptor.',
                'fields'  => array(),
                'item'    => array(),
                'total'   => 0,
                'index'   => 0,
                'type'    => '',
            );
        }

        $parser  = ParserFactory::make( $file['type'] );

        // The parser preview returns:
        // [
        //   'headers' => [...],
        //   'rows'    => [[...], [...]],
        //   'total'   => int,
        // ]
        $preview = $parser->preview( $file['path'], array(
            'xpath'    => $xpath,
            'max_rows' => (int) $args['max_rows'],
            'filters'  => $filters,
        ) );

        $headers = isset( $preview['headers'] ) && is_array( $preview['headers'] )
            ? $preview['headers']
            : array();
        // Drop empty headers to avoid "undefined" items in UI
        $headers = array_values( array_filter( $headers, function( $h ) {
            return '' !== trim( (string) $h );
        } ) );
        $fields_tree = self::build_fields_tree( $headers );

        $rows = isset( $preview['rows'] ) && is_array( $preview['rows'] )
            ? $preview['rows']
            : array();

        if ( ! empty( $filters ) ) {
            $rows = self::apply_filters_to_rows( $headers, $rows, $filters );
        }

        $total = isset( $preview['total'] )
            ? (int) $preview['total']
            : count( $rows );

        if ( $total < 1 ) {
            return array(
                'fields' => $headers,
                'item'   => array(),
                'total'  => 0,
                'index'  => 0,
                'type'   => $file['type'],
                'message'=> 'No rows available for mapping.',
            );
        }

        if ( $index > $total - 1 ) {
            $index = $total - 1;
        }

        $row = $rows[ $index ] ?? array();

        // Build item as associative map Field => Value
        $item_assoc = array();
        foreach ( $headers as $i => $field_name ) {
            $item_assoc[ $field_name ] = isset( $row[ $i ] ) ? $row[ $i ] : '';
        }

        return array(
            'fields' => $headers,
            'fields_tree' => $fields_tree,
            'item'   => $item_assoc,
            'total'  => $total,
            'index'  => $index,
            'type'   => $file['type'],
        );
    }

    private static function build_fields_tree( $headers ) {
        $root = array();
        foreach ( (array) $headers as $h ) {
            $parts = array_filter( explode( '.', (string) $h ) );
            $ref   = &$root;
            $full  = '';
            $last_index = count( $parts ) - 1;
            foreach ( $parts as $i => $part ) {
                $full = ( '' === $full ) ? $part : $full . '.' . $part;
                if ( ! isset( $ref[ $part ] ) ) {
                    $ref[ $part ] = array(
                        '_children' => array(),
                        '_full'     => $full,
                        '_is_field' => false,
                    );
                }
                // Mark this node as a valid field path even if it ends up with children (e.g. lists)
                if ( $i === $last_index ) {
                    $ref[ $part ]['_is_field'] = true;
                }
                $ref = &$ref[ $part ]['_children'];
            }
            unset( $ref );
        }

        // The tree structure already handles duplicates correctly since we use associative arrays
        // Nodes with the same name at the same level are automatically merged

        $walker = function( $node ) use ( &$walker ) {
            $out = array();
            foreach ( $node as $name => $data ) {
                $children = $data['_children'] ?? array();
                $out[] = array(
                    'name'     => $name,
                    'full'     => $data['_full'] ?? $name,
                    'is_field' => ! empty( $data['_is_field'] ),
                    'children' => $walker( $children ),
                );
            }
            return $out;
        };

        return $walker( $root );
    }

    /**
     * Simple filter evaluator for previews (regex, in, contains, numeric).
     */
    private static function apply_filters_to_rows( $headers, $rows, $filters ) {
        if ( empty( $filters ) ) {
            return $rows;
        }

        $headers = array_values( (array) $headers );
        $filtered = array();

        foreach ( (array) $rows as $row ) {
            $assoc = array();
            foreach ( $headers as $idx => $h ) {
                $assoc[ $h ] = isset( $row[ $idx ] ) ? (string) $row[ $idx ] : '';
            }
            if ( self::row_matches_filters( $assoc, $filters ) ) {
                $filtered[] = $row;
            }
        }

        return $filtered;
    }

    private static function row_matches_filters( $assoc, $filters ) {
        foreach ( (array) $filters as $f ) {
            $field = isset( $f['field'] ) ? (string) $f['field'] : '';
            $op    = isset( $f['op'] ) ? (string) $f['op'] : '=';
            $value = isset( $f['value'] ) ? (string) $f['value'] : '';

            $left = isset( $assoc[ $field ] ) ? trim( (string) $assoc[ $field ] ) : '';
            $list = self::split_values( $value );

            $match = true;
            switch ( $op ) {
                case '!=':
                    $match = ( $left !== $value );
                    break;
                case 'contains':
                    $match = ( stripos( $left, $value ) !== false );
                    break;
                case 'not_contains':
                    $match = ( stripos( $left, $value ) === false );
                    break;
                case 'in':
                    $match = empty( $list ) ? false : in_array( strtolower( $left ), array_map( 'strtolower', $list ), true );
                    break;
                case 'not_in':
                    $match = empty( $list ) ? true : ! in_array( strtolower( $left ), array_map( 'strtolower', $list ), true );
                    break;
                case 'regex':
                case 'not_regex':
                    $pattern = '/' . str_replace( '/', '\/', $value ) . '/i';
                    $ok = @preg_match( $pattern, $left );
                    $match = $op === 'regex' ? ( $ok === 1 ) : ( $ok === 0 || $ok === false );
                    break;
                case '>':
                    $match = floatval( $left ) > floatval( $value );
                    break;
                case '<':
                    $match = floatval( $left ) < floatval( $value );
                    break;
                case '>=':
                    $match = floatval( $left ) >= floatval( $value );
                    break;
                case '<=':
                    $match = floatval( $left ) <= floatval( $value );
                    break;
                case '=':
                default:
                    $match = ( $left === $value );
                    break;
            }

            if ( ! $match ) {
                return false;
            }
        }
        return true;
    }

    private static function split_values( $value ) {
        $parts = preg_split( '/\s*[,|;\n\r]+\s*/', (string) $value );
        $parts = array_values( array_filter( array_map( 'trim', $parts ), function( $v ) { return $v !== ''; } ) );
        return $parts;
    }
}
