/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.

For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "passport/ui/passport_details_row.h"

#include "lang/lang_keys.h"
#include "base/platform/base_platform_info.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/masked_input_field.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/layers/box_content.h"
#include "ui/boxes/country_select_box.h"
#include "ui/painter.h"
#include "countries/countries_instance.h"
#include "styles/style_layers.h"
#include "styles/style_passport.h"
#include "base/qt/qt_common_adapters.h"

#include <QtCore/QRegularExpression>

namespace Passport::Ui {
namespace {

class PostcodeInput : public MaskedInputField {
public:
	PostcodeInput(
		QWidget *parent,
		const style::InputField &st,
		rpl::producer<QString> placeholder,
		const QString &val);

protected:
	void correctValue(
		const QString &was,
		int wasCursor,
		QString &now,
		int &nowCursor) override;

};

PostcodeInput::PostcodeInput(
	QWidget *parent,
	const style::InputField &st,
	rpl::producer<QString> placeholder,
	const QString &val)
: MaskedInputField(parent, st, std::move(placeholder), val) {
	static const auto RegExp = QRegularExpression("^[a-zA-Z0-9\\-]+$");
	if (!RegExp.match(val).hasMatch()) {
		setText(QString());
	}
}

void PostcodeInput::correctValue(
		const QString &was,
		int wasCursor,
		QString &now,
		int &nowCursor) {
	QString newText;
	newText.reserve(now.size());
	auto newPos = nowCursor;
	for (auto i = 0, l = int(now.size()); i < l; ++i) {
		const auto ch = now[i];
		if ((ch >= '0' && ch <= '9')
			|| (ch >= 'a' && ch <= 'z')
			|| (ch >= 'A' && ch <= 'Z')
			|| (ch == '-')) {
			newText.append(ch);
		} else if (i < nowCursor) {
			--newPos;
		}
	}
	setCorrectedText(now, nowCursor, newText, newPos);
}

template <typename Input>
class AbstractTextRow : public PanelDetailsRow {
public:
	AbstractTextRow(
		QWidget *parent,
		const QString &label,
		int maxLabelWidth,
		const QString &value,
		int limit);

	bool setFocusFast() override;
	rpl::producer<QString> value() const override;
	QString valueCurrent() const override;

private:
	int resizeInner(int left, int top, int width) override;
	void showInnerError() override;
	void finishInnerAnimating() override;

	object_ptr<Input> _field;
	rpl::variable<QString> _value;

};

class CountryRow : public PanelDetailsRow {
public:
	CountryRow(
		QWidget *parent,
		Fn<void(object_ptr<BoxContent>)> showBox,
		const QString &defaultCountry,
		const QString &label,
		int maxLabelWidth,
		const QString &value);

	rpl::producer<QString> value() const override;
	QString valueCurrent() const override;

private:
	int resizeInner(int left, int top, int width) override;
	void showInnerError() override;
	void finishInnerAnimating() override;

	void chooseCountry();
	void hideCountryError();
	void toggleError(bool shown);
	void errorAnimationCallback();

	QString _defaultCountry;
	Fn<void(object_ptr<BoxContent>)> _showBox;
	object_ptr<LinkButton> _link;
	rpl::variable<QString> _value;
	bool _errorShown = false;
	Animations::Simple _errorAnimation;

};

class DateInput final : public MaskedInputField {
public:
	using MaskedInputField::MaskedInputField;

	void setMaxValue(int value);

	rpl::producer<> erasePrevious() const;
	rpl::producer<QChar> putNext() const;

protected:
	void keyPressEvent(QKeyEvent *e) override;

	void correctValue(
		const QString &was,
		int wasCursor,
		QString &now,
		int &nowCursor) override;

private:
	int _maxValue = 0;
	int _maxDigits = 0;
	rpl::event_stream<> _erasePrevious;
	rpl::event_stream<QChar> _putNext;

};

class DateRow : public PanelDetailsRow {
public:
	DateRow(
		QWidget *parent,
		const QString &label,
		int maxLabelWidth,
		const QString &value);

	bool setFocusFast() override;
	rpl::producer<QString> value() const override;
	QString valueCurrent() const override;

protected:
	void paintEvent(QPaintEvent *e) override;
	void mousePressEvent(QMouseEvent *e) override;
	void mouseMoveEvent(QMouseEvent *e) override;

private:
	void setInnerFocus();
	void putNext(const object_ptr<DateInput> &field, QChar ch);
	void erasePrevious(const object_ptr<DateInput> &field);
	int resizeInner(int left, int top, int width) override;
	void showInnerError() override;
	void finishInnerAnimating() override;
	void setErrorShown(bool error);
	void setFocused(bool focused);
	void startBorderAnimation();
	template <typename Widget>
	bool insideSeparator(QPoint position, const Widget &widget) const;

	int day() const;
	int month() const;
	int year() const;
	int number(const object_ptr<DateInput> &field) const;

	object_ptr<DateInput> _day;
	object_ptr<PaddingWrap<FlatLabel>> _separator1;
	object_ptr<DateInput> _month;
	object_ptr<PaddingWrap<FlatLabel>> _separator2;
	object_ptr<DateInput> _year;
	rpl::variable<QString> _value;

	style::cursor _cursor = style::cur_default;
	Animations::Simple _a_borderShown;
	int _borderAnimationStart = 0;
	Animations::Simple _a_borderOpacity;
	bool _borderVisible = false;

	Animations::Simple _a_error;
	bool _error = false;
	Animations::Simple _a_focused;
	bool _focused = false;

};

class GenderRow : public PanelDetailsRow {
public:
	GenderRow(
		QWidget *parent,
		const QString &label,
		int maxLabelWidth,
		const QString &value);

	rpl::producer<QString> value() const override;
	QString valueCurrent() const override;

private:
	enum class Gender {
		Male,
		Female,
	};

	static std::optional<Gender> StringToGender(const QString &value);
	static QString GenderToString(Gender gender);

	int resizeInner(int left, int top, int width) override;

	void showInnerError() override;
	void finishInnerAnimating() override;
	void toggleError(bool shown);
	void hideGenderError();
	void errorAnimationCallback();

	std::unique_ptr<AbstractCheckView> createRadioView(
		RadioView* &weak) const;

	std::shared_ptr<RadioenumGroup<Gender>> _group;
	RadioView *_maleRadio = nullptr;
	RadioView *_femaleRadio = nullptr;
	object_ptr<Radioenum<Gender>> _male;
	object_ptr<Radioenum<Gender>> _female;
	rpl::variable<QString> _value;

	bool _errorShown = false;
	Animations::Simple _errorAnimation;

};

template <typename Input>
AbstractTextRow<Input>::AbstractTextRow(
	QWidget *parent,
	const QString &label,
	int maxLabelWidth,
	const QString &value,
	int limit)
: PanelDetailsRow(parent, label, maxLabelWidth)
, _field(this, st::passportDetailsField, nullptr, value)
, _value(value) {
	_field->setMaxLength(limit);
	if constexpr (std::is_same<Input, Ui::InputField>::value) {
		_field->changes(
		) | rpl::start_with_next([=] {
			_value = valueCurrent();
		}, _field->lifetime());
	} else {
		connect(_field, &Input::changed, [=] {
			_value = valueCurrent();
		});
	}
}

template <typename Input>
bool AbstractTextRow<Input>::setFocusFast() {
	_field->setFocusFast();
	return true;
}

template <typename Input>
QString AbstractTextRow<Input>::valueCurrent() const {
	return _field->getLastText();
}

template <typename Input>
rpl::producer<QString> AbstractTextRow<Input>::value() const {
	return _value.value();
}

template <typename Input>
int AbstractTextRow<Input>::resizeInner(int left, int top, int width) {
	_field->setGeometry(left, top, width, _field->height());
	return st::semiboldFont->height;
}

template <typename Input>
void AbstractTextRow<Input>::showInnerError() {
	_field->showError();
}

template <typename Input>
void AbstractTextRow<Input>::finishInnerAnimating() {
	_field->finishAnimating();
}

QString CountryString(const QString &code) {
	const auto name = Countries::Instance().countryNameByISO2(code);
	return name.isEmpty() ? tr::lng_passport_country_choose(tr::now) : name;
}

CountryRow::CountryRow(
	QWidget *parent,
	Fn<void(object_ptr<BoxContent>)> showBox,
	const QString &defaultCountry,
	const QString &label,
	int maxLabelWidth,
	const QString &value)
: PanelDetailsRow(parent, label, maxLabelWidth)
, _defaultCountry(defaultCountry)
, _showBox(std::move(showBox))
, _link(this, CountryString(value), st::boxLinkButton)
, _value(value) {
	_value.changes(
	) | rpl::start_with_next([=] {
		hideCountryError();
	}, lifetime());

	_link->addClickHandler([=] {
		chooseCountry();
	});
}

QString CountryRow::valueCurrent() const {
	return _value.current();
}

rpl::producer<QString> CountryRow::value() const {
	return _value.value();
}

int CountryRow::resizeInner(int left, int top, int width) {
	_link->move(left, st::passportDetailsField.textMargins.top() + top);
	return st::semiboldFont->height;
}

void CountryRow::showInnerError() {
	toggleError(true);
}

void CountryRow::finishInnerAnimating() {
	if (_errorAnimation.animating()) {
		_errorAnimation.stop();
		errorAnimationCallback();
	}
}

void CountryRow::hideCountryError() {
	toggleError(false);
}

void CountryRow::toggleError(bool shown) {
	if (_errorShown != shown) {
		_errorShown = shown;
		_errorAnimation.start(
			[=] { errorAnimationCallback(); },
			_errorShown ? 0. : 1.,
			_errorShown ? 1. : 0.,
			st::passportDetailsField.duration);
	}
}

void CountryRow::errorAnimationCallback() {
	const auto error = _errorAnimation.value(_errorShown ? 1. : 0.);
	if (error == 0.) {
		_link->setColorOverride(std::nullopt);
	} else {
		_link->setColorOverride(anim::color(
			st::boxLinkButton.color,
			st::boxTextFgError,
			error));
	}
}

void CountryRow::chooseCountry() {
	const auto top = _value.current();
	const auto name = Countries::Instance().countryNameByISO2(top);
	const auto country = !name.isEmpty()
		? top
		: !_defaultCountry.isEmpty()
		? _defaultCountry
		: Platform::SystemCountry();
	auto box = Box<CountrySelectBox>(
		country,
		CountrySelectBox::Type::Countries);
	const auto raw = box.data();
	raw->countryChosen(
	) | rpl::start_with_next([=](QString iso) {
		_value = iso;
		_link->setText(CountryString(iso));
		hideCountryError();
		raw->closeBox();
	}, lifetime());
	_showBox(std::move(box));
}

QDate ValidateDate(const QString &value) {
	static const auto RegExp = QRegularExpression(
		"^([0-9]{2})\\.([0-9]{2})\\.([0-9]{4})$");
	const auto match = RegExp.match(value);
	if (!match.hasMatch()) {
		return QDate();
	}
	auto result = QDate();
	const auto readInt = [](const QString &value) {
		auto view = QStringView(value);
		while (!view.isEmpty() && view.at(0) == '0') {
			view = base::StringViewMid(view, 1);
		}
		return view.toInt();
	};
	result.setDate(
		readInt(match.captured(3)),
		readInt(match.captured(2)),
		readInt(match.captured(1)));
	return result;
}

QString GetDay(const QString &value) {
	if (const auto date = ValidateDate(value); date.isValid()) {
		return QString("%1").arg(date.day(), 2, 10, QChar('0'));
	}
	return QString();
}

QString GetMonth(const QString &value) {
	if (const auto date = ValidateDate(value); date.isValid()) {
		return QString("%1").arg(date.month(), 2, 10, QChar('0'));
	}
	return QString();
}

QString GetYear(const QString &value) {
	if (const auto date = ValidateDate(value); date.isValid()) {
		return QString("%1").arg(date.year(), 4, 10, QChar('0'));
	}
	return QString();
}

void DateInput::setMaxValue(int value) {
	_maxValue = value;
	_maxDigits = 0;
	while (value > 0) {
		++_maxDigits;
		value /= 10;
	}
}

rpl::producer<> DateInput::erasePrevious() const {
	return _erasePrevious.events();
}

rpl::producer<QChar> DateInput::putNext() const {
	return _putNext.events();
}

void DateInput::keyPressEvent(QKeyEvent *e) {
	const auto isBackspace = (e->key() == Qt::Key_Backspace);
	const auto isBeginning = (cursorPosition() == 0);
	if (isBackspace && isBeginning && !hasSelectedText()) {
		_erasePrevious.fire({});
	} else {
		MaskedInputField::keyPressEvent(e);
	}
}

void DateInput::correctValue(
		const QString &was,
		int wasCursor,
		QString &now,
		int &nowCursor) {
	auto newText = QString();
	auto newCursor = -1;
	const auto oldCursor = nowCursor;
	const auto oldLength = now.size();
	auto accumulated = 0;
	auto limit = 0;
	for (; limit != oldLength; ++limit) {
		if (now[limit].isDigit()) {
			accumulated *= 10;
			accumulated += (now[limit].unicode() - '0');
			if (accumulated > _maxValue || limit == _maxDigits) {
				break;
			}
		}
	}
	for (auto i = 0; i != limit;) {
		if (now[i].isDigit()) {
			newText += now[i];
		}
		if (++i == oldCursor) {
			newCursor = newText.size();
		}
	}
	if (newCursor < 0) {
		newCursor = newText.size();
	}
	if (newText != now) {
		now = newText;
		setText(now);
		startPlaceholderAnimation();
	}
	if (newCursor != nowCursor) {
		nowCursor = newCursor;
		setCursorPosition(nowCursor);
	}
	if (accumulated > _maxValue
		|| (limit == _maxDigits && oldLength > _maxDigits)) {
		if (oldCursor > limit) {
			_putNext.fire(QChar('0' + (accumulated % 10)));
		} else {
			_putNext.fire(QChar(0));
		}
	}
}

DateRow::DateRow(
	QWidget *parent,
	const QString &label,
	int maxLabelWidth,
	const QString &value)
: PanelDetailsRow(parent, label, maxLabelWidth)
, _day(
	this,
	st::passportDetailsDateField,
	tr::lng_date_input_day(),
	GetDay(value))
, _separator1(
	this,
	object_ptr<FlatLabel>(
		this,
		QString(" / "),
		st::passportDetailsSeparator),
	st::passportDetailsSeparatorPadding)
, _month(
	this,
	st::passportDetailsDateField,
	tr::lng_date_input_month(),
	GetMonth(value))
, _separator2(
	this,
	object_ptr<FlatLabel>(
		this,
		QString(" / "),
		st::passportDetailsSeparator),
	st::passportDetailsSeparatorPadding)
, _year(
	this,
	st::passportDetailsDateField,
	tr::lng_date_input_year(),
	GetYear(value))
, _value(valueCurrent()) {
	const auto focused = [=](const object_ptr<DateInput> &field) {
		return [this, pointer = MakeWeak(field.data())]{
			_borderAnimationStart = pointer->borderAnimationStart()
				+ pointer->x()
				- _day->x();
			setFocused(true);
		};
	};
	const auto blurred = [=] {
		setFocused(false);
	};
	const auto changed = [=] {
		_value = valueCurrent();
	};
	connect(_day, &MaskedInputField::focused, focused(_day));
	connect(_month, &MaskedInputField::focused, focused(_month));
	connect(_year, &MaskedInputField::focused, focused(_year));
	connect(_day, &MaskedInputField::blurred, blurred);
	connect(_month, &MaskedInputField::blurred, blurred);
	connect(_year, &MaskedInputField::blurred, blurred);
	connect(_day, &MaskedInputField::changed, changed);
	connect(_month, &MaskedInputField::changed, changed);
	connect(_year, &MaskedInputField::changed, changed);
	_day->setMaxValue(31);
	_day->putNext() | rpl::start_with_next([=](QChar ch) {
		putNext(_month, ch);
	}, lifetime());
	_month->setMaxValue(12);
	_month->putNext() | rpl::start_with_next([=](QChar ch) {
		putNext(_year, ch);
	}, lifetime());
	_month->erasePrevious() | rpl::start_with_next([=] {
		erasePrevious(_day);
	}, lifetime());
	_year->setMaxValue(2999);
	_year->erasePrevious() | rpl::start_with_next([=] {
		erasePrevious(_month);
	}, lifetime());
	_separator1->setAttribute(Qt::WA_TransparentForMouseEvents);
	_separator2->setAttribute(Qt::WA_TransparentForMouseEvents);
	setMouseTracking(true);

	_value.changes(
	) | rpl::start_with_next([=] {
		setErrorShown(false);
	}, lifetime());
}

void DateRow::putNext(const object_ptr<DateInput> &field, QChar ch) {
	field->setCursorPosition(0);
	if (ch.unicode()) {
		field->setText(ch + field->getLastText());
		field->setCursorPosition(1);
	}
	field->setFocus();
}

void DateRow::erasePrevious(const object_ptr<DateInput> &field) {
	const auto text = field->getLastText();
	if (!text.isEmpty()) {
		field->setCursorPosition(text.size() - 1);
		field->setText(text.mid(0, text.size() - 1));
	}
	field->setFocus();
}

bool DateRow::setFocusFast() {
	if (day()) {
		if (month()) {
			_year->setFocusFast();
		} else {
			_month->setFocusFast();
		}
	} else {
		_day->setFocusFast();
	}
	return true;
}

int DateRow::number(const object_ptr<DateInput> &field) const {
	const auto text = field->getLastText();
	auto view = QStringView(text);
	while (!view.isEmpty() && view.at(0) == '0') {
		view = base::StringViewMid(view, 1);
	}
	return view.toInt();
}

int DateRow::day() const {
	return number(_day);
}

int DateRow::month() const {
	return number(_month);
}

int DateRow::year() const {
	return number(_year);
}

QString DateRow::valueCurrent() const {
	const auto result = QString("%1.%2.%3"
		).arg(day(), 2, 10, QChar('0')
		).arg(month(), 2, 10, QChar('0')
		).arg(year(), 4, 10, QChar('0'));
	return ValidateDate(result).isValid() ? result : QString();
}

rpl::producer<QString> DateRow::value() const {
	return _value.value();
}

void DateRow::paintEvent(QPaintEvent *e) {
	PanelDetailsRow::paintEvent(e);

	Painter p(this);

	const auto &_st = st::passportDetailsField;
	const auto height = _st.heightMin;
	const auto width = _year->x() + _year->width() - _day->x();
	p.translate(_day->x(), _day->y());
	if (_st.border) {
		p.fillRect(0, height - _st.border, width, _st.border, _st.borderFg);
	}
	auto errorDegree = _a_error.value(_error ? 1. : 0.);
	auto borderShownDegree = _a_borderShown.value(1.);
	auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
	if (_st.borderActive && (borderOpacity > 0.)) {
		auto borderStart = std::clamp(_borderAnimationStart, 0, width);
		auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
		auto borderTo = borderStart + qRound((width - borderStart) * borderShownDegree);
		if (borderTo > borderFrom) {
			auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree);
			p.setOpacity(borderOpacity);
			p.fillRect(borderFrom, height - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg);
			p.setOpacity(1);
		}
	}
}

template <typename Widget>
bool DateRow::insideSeparator(QPoint position, const Widget &widget) const {
	const auto x = position.x();
	const auto y = position.y();
	return (x >= widget->x() && x < widget->x() + widget->width())
		&& (y >= _day->y() && y < _day->y() + _day->height());
}

void DateRow::mouseMoveEvent(QMouseEvent *e) {
	const auto cursor = (insideSeparator(e->pos(), _separator1)
		|| insideSeparator(e->pos(), _separator2))
		? style::cur_text
		: style::cur_default;
	if (_cursor != cursor) {
		_cursor = cursor;
		setCursor(_cursor);
	}
}

void DateRow::mousePressEvent(QMouseEvent *e) {
	const auto x = e->pos().x();
	const auto focus1 = [&] {
		if (_day->getLastText().size() > 1) {
			_month->setFocus();
		} else {
			_day->setFocus();
		}
	};
	if (insideSeparator(e->pos(), _separator1)) {
		focus1();
		_borderAnimationStart = x - _day->x();
	} else if (insideSeparator(e->pos(), _separator2)) {
		if (_month->getLastText().size() > 1) {
			_year->setFocus();
		} else {
			focus1();
		}
		_borderAnimationStart = x - _day->x();
	}
}

int DateRow::resizeInner(int left, int top, int width) {
	const auto right = left + width;
	const auto &_st = st::passportDetailsDateField;
	const auto &font = _st.placeholderFont;
	const auto addToWidth = st::passportDetailsSeparatorPadding.left();
	const auto dayWidth = _st.textMargins.left()
		+ _st.placeholderMargins.left()
		+ font->width(tr::lng_date_input_day(tr::now))
		+ _st.placeholderMargins.right()
		+ _st.textMargins.right()
		+ addToWidth;
	const auto monthWidth = _st.textMargins.left()
		+ _st.placeholderMargins.left()
		+ font->width(tr::lng_date_input_month(tr::now))
		+ _st.placeholderMargins.right()
		+ _st.textMargins.right()
		+ addToWidth;
	_day->setGeometry(left, top, dayWidth, _day->height());
	left += dayWidth - addToWidth;
	_separator1->resizeToNaturalWidth(width);
	_separator1->move(left, top);
	left += _separator1->width();
	_month->setGeometry(left, top, monthWidth, _month->height());
	left += monthWidth - addToWidth;
	_separator2->resizeToNaturalWidth(width);
	_separator2->move(left, top);
	left += _separator2->width();
	_year->setGeometry(left, top, right - left, _year->height());
	return st::semiboldFont->height;
}

void DateRow::showInnerError() {
	setErrorShown(true);
	if (_year->getLastText().size() == 2) {
		// We don't support year 95 for 1995 or 03 for 2003.
		// Let's give a hint to our user what is wrong.
		_year->setFocus();
		_year->selectAll();
	} else if (!_focused) {
		setInnerFocus();
	}
}

void DateRow::setInnerFocus() {
	if (day()) {
		if (month()) {
			_year->setFocus();
		} else {
			_month->setFocus();
		}
	} else {
		_day->setFocus();
	}
}

void DateRow::setErrorShown(bool error) {
	if (_error != error) {
		_error = error;
		_a_error.start(
			[=] { update(); },
			_error ? 0. : 1.,
			_error ? 1. : 0.,
			st::passportDetailsField.duration);
		startBorderAnimation();
	}
}

void DateRow::setFocused(bool focused) {
	if (_focused != focused) {
		_focused = focused;
		_a_focused.start(
			[=] { update(); },
			_focused ? 0. : 1.,
			_focused ? 1. : 0.,
			st::passportDetailsField.duration);
		startBorderAnimation();
	}
}

void DateRow::finishInnerAnimating() {
	_day->finishAnimating();
	_month->finishAnimating();
	_year->finishAnimating();
	_a_borderOpacity.stop();
	_a_borderShown.stop();
	_a_error.stop();
}

void DateRow::startBorderAnimation() {
	auto borderVisible = (_error || _focused);
	if (_borderVisible != borderVisible) {
		_borderVisible = borderVisible;
		const auto duration = st::passportDetailsField.duration;
		if (_borderVisible) {
			if (_a_borderOpacity.animating()) {
				_a_borderOpacity.start([=] { update(); }, 0., 1., duration);
			} else {
				_a_borderShown.start([=] { update(); }, 0., 1., duration);
			}
		} else {
			_a_borderOpacity.start([=] { update(); }, 1., 0., duration);
		}
	}
}

GenderRow::GenderRow(
	QWidget *parent,
	const QString &label,
	int maxLabelWidth,
	const QString &value)
: PanelDetailsRow(parent, label, maxLabelWidth)
, _group(StringToGender(value).has_value()
	? std::make_shared<RadioenumGroup<Gender>>(*StringToGender(value))
	: std::make_shared<RadioenumGroup<Gender>>())
, _male(
	this,
	_group,
	Gender::Male,
	tr::lng_passport_gender_male(tr::now),
	st::defaultCheckbox,
	createRadioView(_maleRadio))
, _female(
	this,
	_group,
	Gender::Female,
	tr::lng_passport_gender_female(tr::now),
	st::defaultCheckbox,
	createRadioView(_femaleRadio))
, _value(StringToGender(value) ? value : QString()) {
	_group->setChangedCallback([=](Gender gender) {
		_value = GenderToString(gender);
		hideGenderError();
	});
}

std::unique_ptr<AbstractCheckView> GenderRow::createRadioView(
		RadioView* &weak) const {
	auto result = std::make_unique<RadioView>(st::defaultRadio, false);
	weak = result.get();
	return result;
}

auto GenderRow::StringToGender(const QString &value)
-> std::optional<Gender> {
	if (value == u"male"_q) {
		return Gender::Male;
	} else if (value == u"female"_q) {
		return Gender::Female;
	}
	return std::nullopt;
}

QString GenderRow::GenderToString(Gender gender) {
	return (gender == Gender::Male) ? "male" : "female";
}

QString GenderRow::valueCurrent() const {
	return _value.current();
}

rpl::producer<QString> GenderRow::value() const {
	return _value.value();
}

int GenderRow::resizeInner(int left, int top, int width) {
	top += st::passportDetailsField.textMargins.top();
	top -= st::defaultCheckbox.textPosition.y();
	_male->moveToLeft(left, top);
	left += _male->widthNoMargins() + st::passportDetailsGenderSkip;
	_female->moveToLeft(left, top);
	return st::semiboldFont->height;
}

void GenderRow::showInnerError() {
	toggleError(true);
}

void GenderRow::finishInnerAnimating() {
	if (_errorAnimation.animating()) {
		_errorAnimation.stop();
		errorAnimationCallback();
	}
}

void GenderRow::hideGenderError() {
	toggleError(false);
}

void GenderRow::toggleError(bool shown) {
	if (_errorShown != shown) {
		_errorShown = shown;
		_errorAnimation.start(
			[=] { errorAnimationCallback(); },
			_errorShown ? 0. : 1.,
			_errorShown ? 1. : 0.,
			st::passportDetailsField.duration);
	}
}

void GenderRow::errorAnimationCallback() {
	const auto error = _errorAnimation.value(_errorShown ? 1. : 0.);
	if (error == 0.) {
		_maleRadio->setUntoggledOverride(std::nullopt);
		_femaleRadio->setUntoggledOverride(std::nullopt);
	} else {
		const auto color = anim::color(
			st::defaultRadio.untoggledFg,
			st::boxTextFgError,
			error);
		_maleRadio->setUntoggledOverride(color);
		_femaleRadio->setUntoggledOverride(color);
	}
}

} // namespace

PanelDetailsRow::PanelDetailsRow(
	QWidget *parent,
	const QString &label,
	int maxLabelWidth)
: _label(label)
, _maxLabelWidth(maxLabelWidth) {
}

object_ptr<PanelDetailsRow> PanelDetailsRow::Create(
		QWidget *parent,
		Fn<void(object_ptr<BoxContent>)> showBox,
		const QString &defaultCountry,
		Type type,
		const QString &label,
		int maxLabelWidth,
		const QString &value,
		const QString &error,
		int limit) {
	auto result = [&]() -> object_ptr<PanelDetailsRow> {
		switch (type) {
		case Type::Text:
			return object_ptr<AbstractTextRow<InputField>>(
				parent,
				label,
				maxLabelWidth,
				value,
				limit);
		case Type::Postcode:
			return object_ptr<AbstractTextRow<PostcodeInput>>(
				parent,
				label,
				maxLabelWidth,
				value,
				limit);
		case Type::Country:
			return object_ptr<CountryRow>(
				parent,
				showBox,
				defaultCountry,
				label,
				maxLabelWidth,
				value);
		case Type::Gender:
			return object_ptr<GenderRow>(
				parent,
				label,
				maxLabelWidth,
				value);
		case Type::Date:
			return object_ptr<DateRow>(
				parent,
				label,
				maxLabelWidth,
				value);
		default:
			Unexpected("Type in PanelDetailsRow::Create.");
		}
	}();
	if (!error.isEmpty()) {
		result->showError(error);
		result->finishAnimating();
	}
	return result;
}

int PanelDetailsRow::LabelWidth(const QString &label) {
	return st::semiboldFont->width(label);
}

bool PanelDetailsRow::setFocusFast() {
	return false;
}

int PanelDetailsRow::resizeGetHeight(int newWidth) {
	const auto padding = st::passportDetailsPadding;
	const auto inputLeft = padding.left() + std::max(
		st::passportDetailsFieldLeft,
		_maxLabelWidth + st::passportDetailsFieldSkipMin);
	const auto inputTop = st::passportDetailsFieldTop;
	const auto inputRight = padding.right();
	const auto inputWidth = std::max(newWidth - inputLeft - inputRight, 0);
	const auto innerHeight = resizeInner(inputLeft, inputTop, inputWidth);
	const auto result = padding.top()
		+ innerHeight
		+ (_error ? _error->height() : 0)
		+ padding.bottom();
	if (_error) {
		_error->resizeToWidth(inputWidth);
		_error->moveToLeft(inputLeft, result - _error->height());
	}
	return result;
}

void PanelDetailsRow::showError(std::optional<QString> error) {
	if (!_errorHideSubscription) {
		_errorHideSubscription = true;

		value(
		) | rpl::start_with_next([=] {
			hideError();
		}, lifetime());
	}
	showInnerError();
	startErrorAnimation(true);
	if (!error.has_value()) {
		return;
	}
	if (error->isEmpty()) {
		if (_error) {
			_error->hide(anim::type::normal);
		}
	} else {
		if (!_error) {
			_error.create(
				this,
				object_ptr<FlatLabel>(
					this,
					*error,
					st::passportVerifyErrorLabel));
		} else {
			_error->entity()->setText(*error);
		}
		_error->heightValue(
		) | rpl::start_with_next([=] {
			resizeToWidth(width());
		}, _error->lifetime());
		_error->show(anim::type::normal);
	}
}

bool PanelDetailsRow::errorShown() const {
	return _errorShown;
}

void PanelDetailsRow::hideError() {
	startErrorAnimation(false);
	if (_error) {
		_error->hide(anim::type::normal);
	}
}

void PanelDetailsRow::startErrorAnimation(bool shown) {
	if (_errorShown != shown) {
		_errorShown = shown;
		_errorAnimation.start(
			[=] { update(); },
			_errorShown ? 0. : 1.,
			_errorShown ? 1. : 0.,
			st::passportDetailsField.duration);
	}
}

void PanelDetailsRow::finishAnimating() {
	if (_error) {
		_error->finishAnimating();
	}
	if (_errorAnimation.animating()) {
		_errorAnimation.stop();
		update();
	}
}

void PanelDetailsRow::paintEvent(QPaintEvent *e) {
	Painter p(this);

	const auto error = _errorAnimation.value(_errorShown ? 1. : 0.);
	p.setFont(st::semiboldFont);
	p.setPen(anim::pen(
		st::passportDetailsField.placeholderFg,
		st::passportDetailsField.placeholderFgError,
		error));
	const auto padding = st::passportDetailsPadding;
	p.drawTextLeft(padding.left(), padding.top(), width(), _label);
}

} // namespace Passport::Ui
