<?php
namespace PrimeLink\Modules\Imports\Parsers;

use PrimeLink\Modules\Imports\Parsers\XmlHelper;

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

final class XmlParser implements ParserInterface {

    public function preview( $path, array $options = array() ) {
        $xpath    = isset( $options['xpath'] ) ? trim( $options['xpath'] ) : '';
        $max_rows = isset( $options['max_rows'] ) ? absint( $options['max_rows'] ) : 0;

        // If explicit XPath, use SimpleXML xpath for accuracy on that node set.
        if ( $xpath ) {
            libxml_use_internal_errors( true );
            $xml = simplexml_load_file( $path );
            if ( ! $xml ) {
                return array( 'headers' => array(), 'rows' => array(), 'total' => 0, 'message' => 'XML could not be read.' );
            }
            $nodes = $xml->xpath( $xpath );
            if ( ! is_array( $nodes ) ) {
                return array( 'headers' => array(), 'rows' => array(), 'total' => 0, 'message' => 'No nodes match XPath.' );
            }
            $rows_assoc = array();
            $count      = 0;
            foreach ( $nodes as $node ) {
                if ( $max_rows > 0 && $count >= $max_rows ) {
                    break;
                }
                $rows_assoc[] = json_decode( json_encode( $node ), true );
                $count++;
            }
            $total   = count( $nodes );
            $headers = array();
            foreach ( $rows_assoc as $row ) {
                if ( is_array( $row ) ) {
                    $flat    = self::flatten_row( $row );
                    $headers = array_unique( array_merge( $headers, array_keys( $flat ) ) );
                }
            }
            $rows = array();
            foreach ( $rows_assoc as $row ) {
                $row  = self::flatten_row( $row );
                $line = array();
                foreach ( $headers as $key ) {
                    $val = isset( $row[ $key ] ) ? $row[ $key ] : '';
                    $line[] = self::stringify_value( $val );
                }
                $rows[] = $line;
            }
            return array(
                'headers' => $headers,
                'rows'    => $rows,
                'total'   => $total,
            );
        }

        // No XPath: derive target node name and depth automatically.
        $target      = '';
        $targetDepth = null;

        $reader = new \XMLReader();
        if ( ! $reader->open( $path ) ) {
            return array(
                'headers' => array(),
                'rows'    => array(),
                'total'   => 0,
                'message' => 'XML could not be read.',
            );
        }

        $rows_assoc = array();
        $total      = 0;

        while ( $reader->read() ) {
            if ( $reader->nodeType !== \XMLReader::ELEMENT ) {
                continue;
            }

            // Auto-detect target when no xpath: pick most frequent path using helper.
            if ( '' === $target ) {
                $roots = XmlHelper::scan_roots( $path, array( 'limit' => 500 ) );
                if ( ! empty( $roots ) ) {
                    usort( $roots, function( $a, $b ) {
                        $da = substr_count( $a['path'], '/' );
                        $db = substr_count( $b['path'], '/' );
                        if ( $da === $db ) {
                            return ( $b['count'] ?? 0 ) <=> ( $a['count'] ?? 0 );
                        }
                        return $db <=> $da;
                    } );
                    $best = $roots[0];
                    $segments = array_filter( explode( '/', $best['path'] ) );
                    if ( ! empty( $segments ) ) {
                        $target      = end( $segments );
                        $targetDepth = count( $segments ) - 1;
                    }
                }
                // Fallback: depth 1
                if ( '' === $target && $reader->depth === 1 ) {
                    $target      = $reader->name;
                    $targetDepth = 1;
                }
                // Prefer <product> if present at depth 1
                if ( '' === $target && $reader->depth === 1 && strtolower( $reader->name ) === 'product' ) {
                    $target      = $reader->name;
                    $targetDepth = 1;
                }
            }

            if ( $target && $reader->name !== $target ) {
                continue;
            }
            if ( null !== $targetDepth && $reader->depth !== $targetDepth ) {
                continue;
            }

            $total++;
            if ( $max_rows > 0 && $total > $max_rows ) {
                // only count, don't collect more rows
                continue;
            }

            $outer = $reader->readOuterXML();
            if ( $outer ) {
                $node = simplexml_load_string( $outer );
                if ( $node ) {
                    $rows_assoc[] = json_decode( json_encode( $node ), true );
                }
            }
        }

        $reader->close();

        // Build headers from union of keys (shallow).
        $headers = array();
        foreach ( $rows_assoc as $row ) {
            if ( is_array( $row ) ) {
                $flat = self::flatten_row( $row );
                $headers = array_unique( array_merge( $headers, array_keys( $flat ) ) );
            }
        }

        // Normalize rows to numeric arrays in header order.
        $rows = array();
        foreach ( $rows_assoc as $row ) {
            $row = self::flatten_row( $row );
            $line = array();
            foreach ( $headers as $key ) {
                $val = isset( $row[ $key ] ) ? $row[ $key ] : '';
                $line[] = self::stringify_value( $val );
            }
            $rows[] = $line;
        }

        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
        );
    }

    private static function flatten_row( $row, $prefix = '' ) {
        $out = array();
        if ( ! is_array( $row ) ) {
            return $out;
        }
        foreach ( $row as $key => $val ) {
            if ( '@attributes' === $key && is_array( $val ) ) {
                foreach ( $val as $attr => $aval ) {
                    $out[ self::join_key( $prefix, '@' . $attr ) ] = $aval;
                }
                continue;
            }

            $fullKey = self::join_key( $prefix, $key );

            if ( is_array( $val ) ) {
                if ( self::is_list( $val ) ) {
                    // Preserve combined plus indexed keys for lists (e.g. images.image.0, images.image.1)
                    $out[ $fullKey ] = self::stringify_value( $val );
                    // Also expose a wildcard-friendly alias to simplify mapping arrays.
                    $out[ $fullKey . '[]' ] = self::stringify_value( $val );
                    foreach ( $val as $i => $child ) {
                        $childKey = self::join_key( $fullKey, $i );
                        if ( is_array( $child ) ) {
                            $nested = self::flatten_row( $child, $childKey );
                            $out    = array_merge( $out, $nested );
                        } else {
                            $out[ $childKey ] = $child;
                        }
                    }
                } else {
                    $nested = self::flatten_row( $val, $fullKey );
                    $out    = array_merge( $out, $nested );
                }
            } else {
                $out[ $fullKey ] = $val;
            }
        }
        return $out;
    }

    private static function join_key( $prefix, $key ) {
        if ( '' === $prefix ) {
            return $key;
        }
        return $prefix . '.' . $key;
    }

    private static function is_list( array $array ) {
        if ( $array === array() ) {
            return true;
        }
        return array_keys( $array ) === range( 0, count( $array ) - 1 );
    }

    private static function stringify_value( $val ) {
        if ( is_array( $val ) ) {
            $scalars = array();
            $iterator = function( $item ) use ( &$scalars, &$iterator ) {
                if ( is_scalar( $item ) || is_null( $item ) ) {
                    $scalars[] = trim( (string) $item );
                } elseif ( is_array( $item ) ) {
                    foreach ( $item as $v ) {
                        $iterator( $v );
                    }
                }
            };
            $iterator( $val );
            $scalars = array_filter( $scalars, function( $s ) { return $s !== ''; } );
            if ( ! empty( $scalars ) ) {
                return implode( ', ', $scalars );
            }
            // All empty -> return empty string instead of [].
            return '';
        }
        return trim( (string) $val );
    }
}
