<?php

/**
 * AP Family Archive - basic text reports (spec-02 free tier).
 *
 * [apfaar_ahnentafel]  - ancestor list with true ahnentafel numbering
 *                      (1 = subject, father = 2n, mother = 2n+1).
 * [apfaar_descendants] - descendant list with d'Aboville-style numbering
 *                      (1, 1.1, 1.2, 1.2.1 ...).
 *
 * These are TEXT reports - the standard genealogical numbering systems are
 * public-domain methodology. Graphical charts belong to AP Family Trees
 * (suite decision, spec-02 Section 7). Every person renders through
 * APFAAR_Public::person_link(), so the privacy floor is identical to the rest
 * of the plugin. Generation depth is capped (default 5, hard max 10) and
 * total rendered nodes are bounded so a pathological tree cannot melt a page.
 *
 * @package    APFAAR_Family_Archive
 * @subpackage APFAAR_Family_Archive/public
 * @since      0.1.0
 */
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
/**
 * Text report shortcodes.
 *
 * @since 0.1.0
 */
class APFAAR_Reports {
    /**
     * Hard ceiling on generations either report will walk.
     */
    const MAX_GENERATIONS = 10;

    /**
     * Hard ceiling on rendered descendant nodes.
     */
    const MAX_NODES = 2000;

    /**
     * The public renderer (owns person_link and page mapping).
     *
     * @var APFAAR_Public
     */
    private $renderer;

    /**
     * Constructor.
     *
     * @param APFAAR_Public $renderer The public shortcode renderer.
     */
    public function __construct( APFAAR_Public $renderer ) {
        $this->renderer = $renderer;
    }

    /**
     * Register the report shortcodes.
     *
     * @return void
     */
    public function register_shortcodes() {
        add_shortcode( 'apfaar_ahnentafel', array($this, 'sc_ahnentafel') );
        add_shortcode( 'apfaar_descendants', array($this, 'sc_descendants') );
    }

    /**
     * Resolve tree + subject person for a report.
     *
     * @param  array $atts Shortcode attributes.
     * @return array       { gedcom, person, error } (person null on error).
     */
    private function subject_for( $atts ) {
        $gedcom = APFAAR_Public::resolve_tree( (array) $atts );
        // Multi-tree aware (P5).
        if ( '' === $gedcom ) {
            return array(
                'gedcom' => '',
                'person' => null,
                'error'  => ( current_user_can( 'manage_options' ) ? '<p class="apfa-admin-notice">' . esc_html__( 'AP Family Archive: no tree selected yet (administrators only see this message).', 'ap-family-archive' ) . '</p>' : '' ),
            );
        }
        $pid = APFAAR_Public::param( 'apfaar_pid' );
        if ( '' === $pid && isset( $atts['person'] ) ) {
            $pid = sanitize_text_field( (string) $atts['person'] );
        }
        if ( '' === $pid ) {
            $error = '<p class="apfa-empty">' . esc_html__( 'No person selected.', 'ap-family-archive' );
            if ( APFAAR_Public::has_page( 'surnames' ) ) {
                $error .= ' <a href="' . esc_url( APFAAR_Public::page_url( 'surnames' ) ) . '">' . esc_html__( 'Browse the surname index to pick one.', 'ap-family-archive' ) . '</a>';
            }
            return array(
                'gedcom' => $gedcom,
                'person' => null,
                'error'  => $error . '</p>',
            );
        }
        $person = APFAAR_People::get_person( $gedcom, $pid );
        if ( !$person ) {
            // Missing and not-viewable read identically (privacy convention).
            return array(
                'gedcom' => $gedcom,
                'person' => null,
                'error'  => '<p class="apfa-empty">' . esc_html__( 'This person is not available.', 'ap-family-archive' ) . '</p>',
            );
        }
        return array(
            'gedcom' => $gedcom,
            'person' => $person,
            'error'  => '',
        );
    }

    /**
     * Generations to walk: shortcode attr, else default 5, ceiling MAX.
     *
     * @param  array $atts Shortcode attributes.
     * @return int
     */
    private function generations_for( $atts ) {
        $g = ( isset( $atts['generations'] ) ? absint( $atts['generations'] ) : 5 );
        return max( 2, min( self::MAX_GENERATIONS, $g ) );
    }

    /**
     * P6 (the extension package) extras under the report header: the "Download as PDF" button
     * for the current view + the owner's saved reports of this type.
     *
     * @param  string $type    ahnentafel|descendants.
     * @param  string $gedcom  Tree identifier.
     * @param  string $pid     Current subject personID.
     * @param  int    $gens    Current generations.
     * @return string
     */
    private function report_extras(
        $type,
        $gedcom,
        $pid,
        $gens
    ) {
        return '';
    }

    /**
     * Back-link to the subject's person page.
     *
     * @param  object $person Subject row.
     * @return string
     */
    private function subject_header( $person ) {
        return '<p class="apfa-report-subject">' . $this->renderer->person_link( $person ) . '</p>';
    }

    // ── [apfaar_ahnentafel] ─────────────────────────────────────────────────────
    /**
     * Ahnentafel (ancestor table) report.
     *
     * @param  array $atts tree, person, generations.
     * @return string
     */
    public function sc_ahnentafel( $atts ) {
        $atts = (array) $atts;
        $subject = $this->subject_for( $atts );
        if ( !$subject['person'] ) {
            return $subject['error'];
        }
        $gedcom = $subject['gedcom'];
        $generations = $this->generations_for( $atts );
        // Walk: ahnentafel number => person row. Father of n is 2n, mother 2n+1.
        $table = array(
            1 => $subject['person'],
        );
        for ($gen = 1; $gen < $generations; $gen++) {
            $first = 2 ** ($gen - 1);
            $last = 2 ** $gen - 1;
            $found = false;
            for ($n = $first; $n <= $last; $n++) {
                if ( !isset( $table[$n] ) ) {
                    continue;
                }
                $family = APFAAR_People::parents_family( $gedcom, $table[$n] );
                if ( !$family ) {
                    continue;
                }
                $father = ( !empty( $family->husband ) ? APFAAR_People::get_person_raw( $gedcom, $family->husband ) : null );
                $mother = ( !empty( $family->wife ) ? APFAAR_People::get_person_raw( $gedcom, $family->wife ) : null );
                if ( $father ) {
                    $table[2 * $n] = $father;
                    $found = true;
                }
                if ( $mother ) {
                    $table[2 * $n + 1] = $mother;
                    $found = true;
                }
            }
            if ( !$found ) {
                break;
            }
        }
        $out = APFAAR_Public::wrap_open( 'apfa-report apfa-ahnentafel' );
        $out .= '<h2>' . esc_html__( 'Ahnentafel', 'ap-family-archive' ) . '</h2>';
        $out .= $this->subject_header( $subject['person'] );
        $out .= $this->report_extras(
            'ahnentafel',
            $gedcom,
            $subject['person']->personID,
            $generations
        );
        $max_num = max( array_keys( $table ) );
        $gens = (int) floor( log( $max_num, 2 ) ) + 1;
        for ($gen = 1; $gen <= $gens; $gen++) {
            $first = 2 ** ($gen - 1);
            $last = 2 ** $gen - 1;
            $rows = '';
            for ($n = $first; $n <= $last; $n++) {
                if ( !isset( $table[$n] ) ) {
                    continue;
                }
                $rows .= '<li value="' . (int) $n . '">' . $this->renderer->person_link( $table[$n] ) . '</li>';
            }
            if ( '' === $rows ) {
                continue;
            }
            /* translators: %d: generation number. */
            $out .= '<h3>' . esc_html( sprintf( __( 'Generation %d', 'ap-family-archive' ), $gen ) ) . '</h3>';
            $out .= '<ol class="apfa-ahnentafel-list">' . $rows . '</ol>';
        }
        return $out . '</div>';
    }

    // ── [apfaar_descendants] ────────────────────────────────────────────────────
    /**
     * Descendant list report (d'Aboville numbering).
     *
     * @param  array $atts tree, person, generations.
     * @return string
     */
    public function sc_descendants( $atts ) {
        $atts = (array) $atts;
        $subject = $this->subject_for( $atts );
        if ( !$subject['person'] ) {
            return $subject['error'];
        }
        $gedcom = $subject['gedcom'];
        $generations = $this->generations_for( $atts );
        $out = APFAAR_Public::wrap_open( 'apfa-report apfa-descendants' );
        $out .= '<h2>' . esc_html__( 'Descendants', 'ap-family-archive' ) . '</h2>';
        $out .= $this->subject_header( $subject['person'] );
        $out .= $this->report_extras(
            'descendants',
            $gedcom,
            $subject['person']->personID,
            $generations
        );
        $nodes = 0;
        $overflow = false;
        $out .= '<ul class="apfa-descend-list">' . $this->descend_item(
            $gedcom,
            $subject['person'],
            '1',
            1,
            $generations,
            $nodes,
            $overflow
        ) . '</ul>';
        if ( $overflow ) {
            $out .= '<p class="apfa-empty">' . esc_html__( 'List truncated - reduce the generations to see a complete branch.', 'ap-family-archive' ) . '</p>';
        }
        return $out . '</div>';
    }

    /**
     * Render one person + their descendants recursively.
     *
     * @param  string $gedcom      Tree identifier.
     * @param  object $person      Person row (raw).
     * @param  string $number      d'Aboville number of this person.
     * @param  int    $gen         Current generation (1-based).
     * @param  int    $generations Depth limit.
     * @param  int    $nodes       Rendered node counter (by ref).
     * @param  bool   $overflow    Set when MAX_NODES hit (by ref).
     * @return string
     */
    private function descend_item(
        $gedcom,
        $person,
        $number,
        $gen,
        $generations,
        &$nodes,
        &$overflow
    ) {
        if ( $nodes >= self::MAX_NODES ) {
            $overflow = true;
            return '';
        }
        $nodes++;
        $item = '<li><span class="apfa-daboville">' . esc_html( $number ) . '</span> ' . $this->renderer->person_link( $person );
        if ( $gen < $generations && APFAAR_Privacy::can_view( $person ) ) {
            $children_html = '';
            $child_index = 0;
            $seen = array();
            foreach ( APFAAR_People::spouse_families( $gedcom, $person->personID ) as $family ) {
                // Note the partner inline (no recursion into spouses).
                $spouse_ref = ( $family->husband === $person->personID ? $family->wife : $family->husband );
                if ( !empty( $spouse_ref ) ) {
                    $spouse = APFAAR_People::get_person_raw( $gedcom, $spouse_ref );
                    $item .= ' <span class="apfa-descend-spouse">m. ' . $this->renderer->person_link( $spouse, false ) . '</span>';
                }
                foreach ( APFAAR_People::children_of( $gedcom, $family->familyID ) as $link ) {
                    if ( isset( $seen[$link->personID] ) ) {
                        continue;
                    }
                    $seen[$link->personID] = true;
                    $child = APFAAR_People::get_person_raw( $gedcom, $link->personID );
                    if ( !$child ) {
                        continue;
                    }
                    $child_index++;
                    $children_html .= $this->descend_item(
                        $gedcom,
                        $child,
                        $number . '.' . $child_index,
                        $gen + 1,
                        $generations,
                        $nodes,
                        $overflow
                    );
                    if ( $overflow ) {
                        break 2;
                    }
                }
            }
            if ( '' !== $children_html ) {
                $item .= '<ul>' . $children_html . '</ul>';
            }
        }
        return $item . '</li>';
    }

}
