package PDK::Config::Context::Static;

use utf8;
use v5.30;
use Moose;
use namespace::autoclean;
use Digest::MD5;
use Encode;
use Encode::Guess;
use Time::Piece;

# 配置内容数组引用
has config => (
    is       => 'ro',
    isa      => 'ArrayRef[Str]',
    required => 1,
);

# 配置内容字符串（延迟构建）
has confContent => (
    is      => 'ro',
    isa     => 'Str',
    lazy    => 1,
    builder => '_buildConfContent',
);

# 当前光标位置（读写，供内部使用）
has cursor => (
    is      => 'rw',
    isa     => 'Int',
    default => 0,
);

# 应用角色
with 'PDK::Config::Context::Role';

# 重写 confSign 为延迟且可选
has '+confSign' => (
    required => 0,
    lazy     => 1,
    builder  => '_buildConfSign',
);

# 重写 timestamp 为可选
has '+timestamp' => (
    required => 0,
    builder  => '_buildTimestamp',
);

# 构建配置签名（MD5 哈希）
sub _buildConfSign {
    my $self = shift;
    return Digest::MD5::md5_hex(join("\n", @{$self->config}));
}

# 构建配置内容字符串
sub _buildConfContent {
    my $self = shift;
    return join("\n", @{$self->config});
}

# 构建时间戳
sub _buildTimestamp {
    my $self = shift;
    my $t = localtime;
    return $t->strftime('%Y-%m-%d %H:%M:%S');
}

# 构建行解析标志数组
sub _buildLineParsedFlags {
    my $self = shift;
    return [map { 0 } (1 .. @{$self->config})];
}

# 将光标移动到配置开头
sub goToHead {
    my $self = shift;
    $self->cursor(0);
    return;
}

# 获取下一行配置
sub nextLine {
    my $self = shift;

    my $result;
    if ($self->cursor < scalar(@{$self->config})) {
        $result = $self->config->[$self->cursor];
        $self->cursor($self->cursor + 1);
    }
    else {
        warn "[nextLine]: 游标溢出，无法移动到下一行";
    }

    return $result;
}

# 移动到上一行配置
# 在移动光标向后之前返回当前行内容
sub prevLine {
    my $self = shift;

    my $result;
    if ($self->cursor > 0) {
        $result = $self->config->[$self->cursor];
        $self->cursor($self->cursor - 1);
    }
    else {
        warn "[prevLine]: 无法移动到上一行，光标已在开头";
    }

    return $result;
}

# 获取下一个未解析的行
# 自动跳过已解析的行和空行，处理字符编码
sub nextUnParsedLine {
    my $self = shift;

    my $result;

    # 跳过所有已解析的行
    while ($self->cursor < scalar(@{$self->config}) and $self->getParseFlag == 1) {
        $self->cursor($self->cursor + 1);
    }

    if ($self->cursor < scalar(@{$self->config})) {
        $result = $self->config->[$self->cursor];

        # 处理空行和仅包含空白字符的行
        while (not defined $result or $result =~ /^\s*$/) {
            $self->setParseFlag(1);
            $self->cursor($self->cursor + 1);

            # 跳过已解析的行
            while ($self->cursor < scalar(@{$self->config}) and $self->getParseFlag == 1) {
                $self->cursor($self->cursor + 1);
            }

            if ($self->cursor < scalar(@{$self->config})) {
                $result = $self->config->[$self->cursor];
            }
            else {
                return undef;
            }
        }

        # 标记当前行为已解析并移动到下一行
        $self->setParseFlag(1);
        $self->cursor($self->cursor + 1);
    }

    return undef unless defined $result;

    # 去除行尾换行符
    chomp $result;

    # 处理字符编码
    my $enc = Encode::Guess->guess($result);

    if (ref $enc) {
        eval { $result = $enc->decode($result); };
        if ($@) {
            warn "[nextUnParsedLine] 字符串解码失败：$@";
        }
    }
    else {
        eval { $result = decode('utf8', $result, Encode::FB_DEFAULT); };
        if ($@) {
            warn "[nextUnParsedLine] 无法猜测编码: $enc，UTF-8 解码也失败: $@";
        }
    }

    return $result;
}

# 回溯到上一行并标记为未解析
# 用于重新处理某行配置
sub backtrack {
    my $self = shift;

    if ($self->cursor > 0) {
        $self->cursor($self->cursor - 1);
        $self->setParseFlag(0);
        return 1;
    }
    else {
        warn "[backtrack]: 无法回溯，光标已在开头";
        return undef;
    }
}

# 忽略当前行（标记为已解析并移动到下一行）
# 等同于回溯后移动到下一行
sub ignore {
    my $self = shift;
    $self->backtrack and $self->nextLine;
}

# 获取所有未解析的行，返回连接后的字符串
sub getUnParsedLines {
    my $self = shift;

    my $unparsed_lines = join(
        '',
        map { $self->config->[$_] }
        grep { $self->lineParsedFlags->[$_] == 0 }
        (0 .. scalar(@{$self->config}) - 1)
    );

    return $unparsed_lines;
}

# 获取当前行的解析标志
# 返回 0（未解析）或 1（已解析），如果光标超出范围则返回 undef
sub getParseFlag {
    my $self = shift;

    if ($self->cursor >= 0 and $self->cursor < scalar(@{$self->config})) {
        return $self->lineParsedFlags->[$self->cursor];
    }
    else {
        return;
    }
}

# 设置当前行的解析标志
# $flag: 0（未解析）或 1（已解析），如果未提供则默认为 1
# 成功返回 1，失败返回 undef
sub setParseFlag {
    my ($self, $flag) = @_;

    if ($self->cursor >= 0 and $self->cursor < scalar(@{$self->config})) {
        $self->lineParsedFlags->[$self->cursor] = $flag // 1;
        return 1;
    }
    else {
        return;
    }
}

__PACKAGE__->meta->make_immutable;
1;

__END__

=encoding utf8

=head1 NAME

PDK::Config::Context::Static - 静态配置内容处理器

=head1 VERSION

0.001

=head1 SYNOPSIS

    use PDK::Config::Context::Static;

    my $config = PDK::Config::Context::Static->new(
        id     => 1,
        name   => 'config-01',
        vendor => 'cisco',
        config => [
            'interface GigabitEthernet0/0',
            ' ip address 192.168.1.1 255.255.255.0',
            ' no shutdown',
        ]
    );

    # 遍历配置行
    while (my $line = $config->nextUnParsedLine) {
        print "处理行: $line\n";
    }

=head1 DESCRIPTION

这是一个静态配置内容处理器，实现了 L<PDK::Config::Context::Role> 角色。
该类提供了对配置文件的解析、遍历和状态跟踪功能。

主要功能包括：
- 配置内容的存储和管理
- 逐行解析配置内容
- 解析状态跟踪（已解析/未解析）
- 配置签名生成（MD5 哈希）
- 时间戳管理
- 光标位置控制

=head1 ATTRIBUTES

=head2 config

配置内容数组引用，必需属性。包含配置的每一行。

    is       => 'ro',
    isa      => 'ArrayRef[Str]',
    required => 1,

=head2 confContent

配置内容字符串，只读属性，延迟构建。通过连接配置数组生成。

    is      => 'ro',
    isa     => 'Str',
    lazy    => 1,
    builder => '_buildConfContent',

=head2 cursor

当前光标位置，读写属性，默认值为 0。

    is      => 'rw',
    isa     => 'Int',
    default => 0,

=head2 confSign

配置签名，继承自角色，延迟构建。使用 MD5 哈希算法生成配置内容的唯一标识。

=head2 timestamp

时间戳，继承自角色，自动构建。记录配置处理的时间。

=head2 lineParsedFlags

行解析标志数组，继承自角色。跟踪每一行的解析状态。

=head1 METHODS

=head2 goToHead

将光标移动到配置开头。

    $config->goToHead;

=head2 nextLine

获取下一行配置内容。如果已到达文件末尾，返回 undef。

    my $line = $config->nextLine;
    if (defined $line) {
        print "当前行: $line\n";
    }

=head2 prevLine

移动到上一行配置。如果已在开头，返回 undef 并输出警告。

    my $line = $config->prevLine;
    if (defined $line) {
        print "上一行: $line\n";
    }

=head2 nextUnParsedLine

获取下一个未解析的行。自动跳过已解析的行和空行，并自动标记为已解析。

    while (my $line = $config->nextUnParsedLine) {
        print "未解析行: $line\n";
        # 处理该行...
    }

=head2 backtrack

回溯到上一行并标记为未解析。用于重新处理某行配置。

    my $success = $config->backtrack;
    if ($success) {
        print "成功回溯\n";
    }

=head2 ignore

忽略当前行（标记为已解析并移动到下一行）。等同于回溯后移动到下一行。

    $config->ignore;

=head2 getUnParsedLines

获取所有未解析的行，返回连接后的字符串。

    my $unparsed = $config->getUnParsedLines;
    print "未解析内容:\n$unparsed\n";

=head2 getParseFlag

获取当前行的解析标志。返回 0（未解析）或 1（已解析）。

    my $flag = $config->getParseFlag;
    if ($flag == 0) {
        print "当前行未解析\n";
    }

=head2 setParseFlag

设置当前行的解析标志。

    $config->setParseFlag(1);  # 标记为已解析
    $config->setParseFlag(0);  # 标记为未解析

=head1 PRIVATE METHODS

=head2 _buildConfSign

使用 MD5 哈希算法构建配置签名。

=head2 _buildConfContent

通过连接配置数组构建配置内容字符串。

=head2 _buildTimestamp

使用 L<Time::Piece> 模块构建时间戳，格式为 "YYYY-MM-DD HH:MM:SS"。

=head2 _buildLineParsedFlags

构建行解析标志数组，初始化为全 0（未解析状态）。

=head1 DEPENDENCIES

- L<Moose>
- L<namespace::autoclean>
- L<Digest::MD5>
- L<Time::Piece> (Perl 核心模块)
- L<Encode>
- L<Encode::Guess>
- L<PDK::Config::Context::Role>

=head1 EXAMPLES

=head2 基本用法

    use PDK::Config::Context::Static;

    my $config = PDK::Config::Context::Static->new(
        id     => 1,
        name   => 'cisco-router',
        vendor => 'cisco',
        config => [
            'interface GigabitEthernet0/0',
            ' ip address 192.168.1.1 255.255.255.0',
            ' no shutdown',
            'interface GigabitEthernet0/1',
            ' ip address 10.0.0.1 255.255.255.0',
            ' no shutdown',
        ]
    );

    # 获取配置签名
    print "配置签名: " . $config->confSign . "\n";

    # 遍历所有未解析的行
    while (my $line = $config->nextUnParsedLine) {
        print "处理: $line\n";
    }

=head2 解析状态跟踪

    # 处理第一行
    my $line1 = $config->nextUnParsedLine;
    print "第一行: $line1\n";

    # 如果需要重新处理，可以回溯
    $config->backtrack;
    $line1 = $config->nextUnParsedLine;  # 重新获取同一行

    # 继续处理剩余行
    while (my $line = $config->nextUnParsedLine) {
        print "继续处理: $line\n";
    }

=head1 AUTHOR

careline <968828@gmail.com>

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2025 careline

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

=cut
